Day 152: what's up with the linking

In Day 152, Casey uses the SafeTruncateUInt64 function inside the PLATFORM_READ_DATA_FROM_FILE implementation.

The function defined in handmade_platform.h as

1
2
3
4
5
6
7
inline uint32
SafeTruncateUInt64(uint64 Value)
{
    Assert(Value <= 0xFFFFFFFF);
    uint32 Result = (uint32)Value;
    return(Result);
}


For some reason, the linker cannot link to this function in my own build unless I define it as static. I'm compiling with clang on OS X. What is the reason for this behaviour and why it did not create the same issue in the video when compiled with MSVC?

There's lots of stuff written on external and internal linkage, it's hard to find a straight answer.

Cheers
How are you compiling? Unity build or multiple .cpp files?
Is handmade_platform.h file included before Win32ReadDataFromFile function?
What is exact error message you are getting?

Edited by Mārtiņš Možeiko on
> How are you compiling? Unity build or multiple .cpp files?
> Is handmade_platform.h file included before Win32ReadDataFromFile function?
Unity build, of course. The header file is included in the very beginning of the main.m platform source file. I'm aiming at maximum compatibility, only the platform code implemented partially with Obj-C: bitmap bliting, sound and event processing is done via Cocoa API to run on OS X.

The structure of the application is identical to what Casey is doing.

I even did an experiment and transferred the definition of the inline function right before the Win32ReadDataFromFile, just to be sure. I still get the same error without static.

The error:
1
2
3
4
Undefined symbols for architecture x86_64:
  "_SafeTruncateUInt64", referenced from:
      _osxReadDataFromFile in main-7e1169.o
ld: symbol(s) not found for architecture x86_64


Compilation:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/usr/bin/clang++ \
    ./src/main.m \
    -g -O0 \
    -pthread \
    -Wall -Werror \
    -pedantic \
    -Wno-unused-variable -Wno-unused-function -Wno-deprecated-declarations \
    -Wno-gnu-anonymous-struct -Wno-nested-anon-types -Wno-switch \
    -Wno-gnu-zero-variadic-macro-arguments \
    -Wno-gnu-empty-initializer -Wno-language-extension-token \
    -DGAME_INTERNAL=1 -DGAME_SLOW=1 \
    -lstdc++ \
    -framework OpenGL -framework Cocoa \
    -framework AudioUnit \
    -o ./build/game

Can you try to remove "-Wno-unused-function" and check if compiler gives any warning/error about SafeTruncateUInt64 function?

Can you try to reduce source code to only main function which calls SafeTruncateUInt64 and still gives same error, and then post the source code here? Following example, obviously, compiles just fine (as Objective C++ code with clang): https://godbolt.org/g/aJd2CI

Without seeing actual source code and modifications you did, it's not easy to guess what could be the problem...


Edited by Mārtiņš Možeiko on
No, it's not related to -Wno-unused-function.

Here's the test build. platform.h:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <stdint.h>

typedef uint32_t uint32;
typedef uint64_t uint64;

#define internal static
#define Assert(Expression) if (!(Expression)) { __builtin_trap(); }

inline uint32
SafeTruncateUInt64(uint64 Value)
{
    Assert(Value <= 0xFFFFFFFF);
    uint32 Result = (uint32)Value;
    return(Result);
}

#define TEST_FUNC(name) uint32 name(uint64 Value)
typedef TEST_FUNC(TestInlineFunction);


main.m:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <stdio.h>
#include "platform.h"

internal TEST_FUNC(platformSafeTruncate)
{
    uint32 FileSize32 = SafeTruncateUInt64(Value);
    return FileSize32;
}

int main() {
    uint64 Size = 12345;
    uint32 Result = platformSafeTruncate(Size);
    printf("%d\n", Result);

    return 0;
}


build.sh
1
/usr/bin/clang++ ./main.m -O0 -Wall -Werror -pedantic -lstdc++ -o ./build/main


It compiles without any problems if I do -O2
Hm, I though if you use clang++ for compiler it will automatically switch to treat source as C++ or Objective-C++. Bet apparently not.
I guess the problem is that "inline" in Objective-C works a bit differently than "inline" in C or C++ or Objective-C++.

Your options would be:
1) change "inline" to "static" and keep it as C/Objective-C source.
2) change "inline" to "static inline" if you really want to keep "inline" keyword there.
3) rename file to main.mm This will make compiler to treat it as Objective-C++ source
4) add -x objective-c++ to compile as Objective-C++ not Objective-C source.

Edited by Mārtiņš Možeiko on
I ended up leaving it as a static function. Renaming to mm brings up more compile errors that I don't want to deal with due to a switch to the c++11 standard and the fourth option simply doesn't work for me.

I would still like to understand how exactly "Objective-C works a bit differently than "inline" in C or C++ or Objective-C++."

Edited by r2d2 on
Yeah, I'm not exactly sure about that - I haven't used Objective-C much.

https://stackoverflow.com/questio...-inline-function-symbol-not-found
Here it suggests that inline in Objective-C is same as in C. If that is the case, then its not so complicated - in C "inline" keyword doesn't require compiler to create definition of function. Because by default functions are "extern" (if you don't put static next to them). So if compiler doesn't decide to actually inline it, it will expect somewhere to have actual function code (because function is extern). That means that at least one translation unit should have actual function definition as non-inline.

Here's similar explanation: https://stackoverflow.com/a/25000931

You can see this behavior here: https://godbolt.org/g/eSoLJB
Compiler decided not to inline any of functions (because optimizations are not enabled) and main calls all three functions - f, g, and h. But translation unit doesn't have code for function f.

Long story short - inline in C is different than inline in C++. Use it only if you understand what you are doing :)

Edited by Mārtiņš Možeiko on