0

I'm looking at code that somehow calls a library but I don't see it calling any of the library's headers at any point. I've done searches within the code and I don't see anywhere the functions, typedefs or structs getting defined anywhere...yet it compiles and works, and LDD shows it calling the library. What is it that's inherent to the C language that allows this to work?

Code that references functions/typedefs/structs non-existent elsewhere in code:

typedef struct pngbuffer_s
{
    byte *buffer;
    png_size_t size, offset;
} pngBuffer_t;
4
  • 2
    without looking at the code you're asking about, it seems rather hard to guess what's going on. See also: Which computer science / programming Stack Exchange do I post in?
    – gnat
    Jul 10, 2015 at 17:32
  • It is a question about the way the C language calls functions and is not specific to just my code. I believe I posted in the right exchange based on those guidelines. Posting code would be pointless unless you really want me to post several thousand of lines of code. Jul 10, 2015 at 17:35
  • 1
    you wrote "I'm looking at code", that can't be thousands lines, one can look at just 10-20 lines in one moment. Even 40 would be too much. Post a snippet, an example that demonstrates what specifically confuses you
    – gnat
    Jul 10, 2015 at 17:39
  • 1
    It's clearly a language issue, so posting code that illustrates exactly what I've described seems pointless, but ok, here goes. Jul 10, 2015 at 17:41

2 Answers 2

2

An external function can (in C) be declared locally, inside some function, or even some block; e.g.

 void myfunction(int x) {
    extern void exit(int);
    if (x==42)
      exit(23);

So you don't need any #include -d header to declare some external function - and later call it -, but that is a lot more convenient. Hence no headers are needed to call any external function, just an extern declaration of it (and before that, any struct, union, typedef, enum ... declarations related to types needed for that external functions).

The example above is using the standard exit function as an example, but you could do something more, e.g. declare a struct and an extern function returning it, like

 if (x>0) {
    struct pair_st { int aa, bb; };
    extern struct pair_st foobar(int, int);
    struct pair_st p = foobar(x+2, x-3);
    exit (p.aa+p.bb);
 }

In addition, a C compiler would consider any undeclared (but called) function as returning an int. You should ask your compiler to issue a warning in that case. If using GCC invoke it with a command starting with gcc -Wall -Wextra -g to get all warnings (-Wall), some extra of them (-Wextra), and debugging info (-g). Then use the debugger (gdb).

You should read more about linkers (e.g. Levine's book on Linkers & Loaders). Generally, they don't care about types or signatures, but just about names (therefore, languages with overloading, like C++, are implemented with compilers doing some name mangling).

At last, some systems have dynamic linkers and are able to load plugins at runtime. My Linux system has (as all POSIX systems) dlopen & dlsym, and in my MELT tool I am doing things similar to:

FILE* fgensrc = fopen("foobar.c", "w");
emit_some_c_code(fgensrc); // a complex function emiting some C code at runtime
fclose (fgensrc);
// now, compile the runtime generated C file foobar.c into a shared object
if (system("gcc -fPIC -Wall -O -shared foobar.c -o ./foobar.so")>0)
   exit(EXIT_FAILURE);
// open the shared object as a plugin
void* dlh = dlopen("./foobar.so", RTLD_NOW|RTLD_GLOBAL);
// declare a signature typedef
typedef int myintsig_t (int, int);
// declare a pointer to a function of that signature
myintsig_t* ptrfun =
// fetch a function pointer by its name at runtime! 
   (myintsig_t*) dlsym(dlh,"foobarfun");
if (!ptrfun) { fputs(stderr, dlerror()); exit(EXIT_FAILURE); };
/// call the function thru the pointer!
x = (*ptrfun) (x, x+3);

In reality, you should add a lot more of error checks.
All the literal strings above "foobar.c", "foobarfun" .... could be computed at runtime!

A function can be called in C indirectly thru a pointer to function. An implementation could provide functions (like dlsym) able to return pointers to other functions.

So you see that on Linux you could emit some C code at runtime, compile it, then access and invoke a function whose name is only known at runtime in that code....

Plugins and their dynamic loading with dlopen is not defined by the C language standard but by POSIX. Some other operating systems (e.g. Windows, which I don't know) also have such dynamic loading abilities.

Notice that the C standard does not know about libraries, only about translation units (or compilation units).

1
  • Amazing! Thank you very much for the information. Jul 21, 2015 at 15:03
3

C assumes that undeclared functions take an arbitrary number of arguments and return an int. Undefined typedefs and structs should always cause an error. My guess would be that one of the files you do include contains the necessary include directive for this library. Most compilers support an option to print out which headers they include as they go through compilation.

3
  • Thank you for your replies. A programmer I just spoke to mentioned that my code could be linking with another object that contains the function being called. I'm almost certain that's what's going on here. Jul 10, 2015 at 18:09
  • @Volumetricsteve that's almost certainly the case, but keep in mind that compiling and linking are separate processes in C. If your code is referring to functions or datatypes defined in a different library, those will need to get declared in your program before it will compile. This is almost certainly happening via the include of some header file. Exactly where it's occuring may be obscured by nesting, For example, you include file "foo.h", which in turn includes "bar.h", which includes "png_magic.h", which provides the needed declarations. Jul 10, 2015 at 19:54
  • Only valid Pre-C99. Jul 11, 2015 at 13:54

Not the answer you're looking for? Browse other questions tagged or ask your own question.