Function pointers in C
Let's start with a basic function which we will be pointing to:
int addInt(int n, int m) {
return n+m;
}
First thing, let's define a pointer to a function which receives 2 int
s and returns an int
:
int (*functionPtr)(int,int);
Now we can safely point to our function:
functionPtr = &addInt;
Now that we have a pointer to the function, let's use it:
int sum = (*functionPtr)(2, 3); // sum == 5
Passing the pointer to another function is basically the same:
int add2to3(int (*functionPtr)(int, int)) {
return (*functionPtr)(2, 3);
}
We can use function pointers in return values as well (try to keep up, it gets messy):
// this is a function called functionFactory which receives parameter n
// and returns a pointer to another function which receives two ints
// and it returns another int
int (*functionFactory(int n))(int, int) {
printf("Got parameter %d", n);
int (*functionPtr)(int,int) = &addInt;
return functionPtr;
}
But it's much nicer to use a typedef
:
typedef int (*myFuncDef)(int, int);
// note that the typedef name is indeed myFuncDef
myFuncDef functionFactory(int n) {
printf("Got parameter %d", n);
myFuncDef functionPtr = &addInt;
return functionPtr;
}
The compilation of a C++ program involves three steps:
Preprocessing: the preprocessor takes a C++ source code file and deals with the #include
s, #define
s and other preprocessor directives. The output of this step is a "pure" C++ file without pre-processor directives.
Compilation: the compiler takes the pre-processor's output and produces an object file from it.
Linking: the linker takes the object files produced by the compiler and produces either a library or an executable file.
Preprocessing
The preprocessor handles the preprocessor directives, like #include
and #define
. It is agnostic of the syntax of C++, which is why it must be used with care.
It works on one C++ source file at a time by replacing #include
directives with the content of the respective files (which is usually just declarations), doing replacement of macros (#define
), and selecting different portions of text depending of #if
, #ifdef
and #ifndef
directives.
The preprocessor works on a stream of preprocessing tokens. Macro substitution is defined as replacing tokens with other tokens (the operator ##
enables merging two tokens when it makes sense).
After all this, the preprocessor produces a single output that is a stream of tokens resulting from the transformations described above. It also adds some special markers that tell the compiler where each line came from so that it can use those to produce sensible error messages.
Some errors can be produced at this stage with clever use of the #if
and #error
directives.
Compilation
The compilation step is performed on each output of the preprocessor. The compiler parses the pure C++ source code (now without any preprocessor directives) and converts it into assembly code. Then invokes underlying back-end(assembler in toolchain) that assembles that code into machine code producing actual binary file in some format(ELF, COFF, a.out, ...). This object file contains the compiled code (in binary form) of the symbols defined in the input. Symbols in object files are referred to by name.
Object files can refer to symbols that are not defined. This is the case when you use a declaration, and don't provide a definition for it. The compiler doesn't mind this, and will happily produce the object file as long as the source code is well-formed.
Compilers usually let you stop compilation at this point. This is very useful because with it you can compile each source code file separately. The advantage this provides is that you don't need to recompile everything if you only change a single file.
The produced object files can be put in special archives called static libraries, for easier reusing later on.
It's at this stage that "regular" compiler errors, like syntax errors or failed overload resolution errors, are reported.
Linking
The linker is what produces the final compilation output from the object files the compiler produced. This output can be either a shared (or dynamic) library (and while the name is similar, they haven't got much in common with static libraries mentioned earlier) or an executable.
It links all the object files by replacing the references to undefined symbols with the correct addresses. Each of these symbols can be defined in other object files or in libraries. If they are defined in libraries other than the standard library, you need to tell the linker about them.
At this stage the most common errors are missing definitions or duplicate definitions. The former means that either the definitions don't exist (i.e. they are not written), or that the object files or libraries where they reside were not given to the linker. The latter is obvious: the same symbol was defined in two different object files or libraries.
Best Solution
I just looked into this and thought some others might be interested in my findings.
Weak linking with weak_import really only works well with dynamic libraries. You can get it to work with static linking (by specifying -undefined dynamic_lookup as suggested above) but this isn't such a hot idea. It means that no undefined symbols will be detected until runtime. This is something I would avoid in production code, personally.
Here is a Mac OS X Terminal session showing how to make it work:
Here is f.c
Here is whatnof.c
Make a dynamic library from f.c:
Compile and link against the dynamic lib, list dynamic libs.
Run whatnof to see what happens:
Now replace f.dylib with an empty library (no symbols):
Run same whatnof to see what happens:
The basic idea (or "use case") for weak_import is that it lets you link against a set of dynamic (shared) libraries, but then run the same code against earlier versions of the same libraries. You can check functions against NULL to see if they're supported in the particular dynamic library that the code is currently running against. This seems to be part of the basic development model supported by Xcode. I hope this example is useful; it has helped put my mind at ease about this part of the Xcode design.