7
votes

Why exactly is it that when I create an iOS static library project or framework project in Xcode, I don't need to link any iOS SDK frameworks to the project in order to make use of their headers and objects -- for example, I can #import <AudioToolbox/AudioToolbox.h> and put AudioToolbox code in the static library or framework without actually having AudioToolbox added under "Link Binary with Libraries" in build settings or having it present in the file navigator, and the project will build without issue, something that wouldn't work in an app project -- but when a developer then uses the static library or framework product in an app, they do have to link to the framework in order to use the same headers and objects?

I have a vague idea of why this would be, but I'd be really interested in hearing from someone who knows for sure.

1

1 Answers

11
votes

Static libraries are just a bundle of .o files. They're not "linked" in any meaningful way; just concatenated together. It's not until you perform a real link step that symbols are resolved.

There is basically no difference between linking a .a with your executable and copying the equivalent source code into your executable's project. So there's no need to link with any additional frameworks or libraries until that time.


The following exercise may be educational:

Create the following comptest.c:

#include <stdio.h>

int main() {
   printf("Hello world.\n");
   return 0;
}

See what the pre-processor does:

gcc -E comptest.c > comptest-cpp.c

This removes the #include and replaces it with the contents of the referenced file. This file is what the compiler actually sees.

Now see what the compiler does (I'm using the > syntax here and below so that things are parallel with -E):

gcc -S comptest.c > comptest.s

This is the generated assembly language after pre-processing and compilation. Now we turn that into a .o:

gcc -c comptest.c > comptest.o

Now let's see what's in that .o:

$ nm comptest.o
0000000000000040 s EH_frame0
000000000000002d s L_.str
0000000000000000 T _main
0000000000000058 S _main.eh
                 U _puts

The important things here are _main and _puts. _main is defined in this file at address 0. _puts is undefined. So something we link with had better provide it. Let's try linking without anything:

$ gcc -nodefaultlibs comptest.o
Undefined symbols for architecture x86_64:
  "_exit", referenced from:
      start in crt1.10.6.o
  "_puts", referenced from:
      _main in comptest.o
ld: symbol(s) not found for architecture x86_64
collect2: ld returned 1 exit status

(_exit is implicit from the C runtime; it's not directly referenced in the .o)

OK, so now we're ready to put it all together. We'll be explicit:

gcc -nodefaultlibs comptest.o /usr/lib/libc.dylib -o comptest

This says to link together comptest.o and the dynamic library libc. It promises that every symbol referenced will be provided by one of these files. It makes a note in the resulting binary that it should dynamically load symbols from /usr/lib/libc.dylib (this is a symlink to libSystem.B.dylib, which is itself an "umbrella framework" rather than a proper library, but that goes a little past what you need to know in most cases; you can pretend that puts() is in libSystem):

$ otool -L comptest
comptest:
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 159.1.0)

If you link with a static library, it's identical to listing all the .o files included in it on the command-line.

Notice that at the link step, we just have .o and .dylib files (.a is just a package of .o). There are no .c files, no .h files, no .s files, no source code. Just object files that need symbols resolved. This is why header files don't matter here, but do matter when you're compiling.