Search code examples
cgccimportcompilationlinker

Can't the gcc linker just find my other files?


So here's something I don't understand : Every single time you include something in c, you have to do #include "dir/subdir/file.h". Since the .c file is in the exact same place, why do I also have to do gcc main.c dir/subdir/file.c -o main ?

It doesn't seem to make sense, every single other language I've used (arguably not that many, but still, several) was able to make sense of imports on its own. Am I missing something ? Is there a way to tell gcc to "just find them yourself" ?


Solution

  • C is a product of early 1970s, and this is one of the areas where that really shows. An #include statement simply loads the contents of the specified file before it gets compiled - the path of the included file isn't being analyzed or preserved anywhere. Similarly, gcc isn't preserving the path of any of the files on the command line - it just loads the source from the specified file, translates it, and spits out an object file at the end.

    C doesn't specify any kind of package management semantics. Each file on the gcc command line is compiled separately, and gcc doesn't know anything about the environment other than what's specified on the command line.

    Yes, you could build a compiler that does all that analysis and tries to be smart about paths and such, but it's a crapload of work, there's no specification for it, no two compilers would do it the same way, and it kind of violates the spirit of C (the programmer is assumed to always know what they're doing, even when they don't).


    Having said all that...

    With gcc, you can use the -I option to specify additional include paths:

    gcc -o main -I /dir/subdir main.c /dir/subdir/file.c 
    

    so in your source you don't need to use an explicit path in the #include:

    #include "file.h"  // no need for /dir/subdir path
    

    Similarly, you can use -L to specify additional directories to search for libraries (linked with -l):

    gcc -o main -I /dir/subdir main.c /dir/subdir/file.c -L /anotherdir/subdir/libs -lmylib.a
    

    Like Eric says, if you're having to manage source across multiple directories, you should be using make or a similar tool - doing all this manually on the command line rapidly gets unscalable.

    EDIT

    I think an example may be helpful. I have a simple C program made up of three files - main.c, foo.c, and bar.c:

    /**
     * main.c
     */
    #include <stdio.h>
    
    /**
     * Notice no explicit path on either include
     */
    #include "foo.h"
    #include "bar.h"
    
    int main( void )
    {
      printf( "calling foo: " );
      foo();
      printf( "calling bar: " );
      bar();
    
      return 0;
    }
    
    /**
     * foo.c
     */
    #include <stdio.h>
    #include "foo.h"
    
    void foo( void )
    {
      puts( "In function foo" );
    }
    
    /**
     * bar.c
     */
    #include <stdio.h>
    #include "bar.h"
    
    void bar( void )
    {
      puts( "In function bar" );
    }
    

    Because I'm insane, I've split the source code up over multiple directories like so:

    make_example/
      Makefile
      build/
      include/
      package/
      src/
        main.c
        subdir.mk
        Makefile
        subdir1/
          Makefile
          foo.c
          foo.h
        subdir2/
          Makefile
          bar.c
          bar.h
       
    

    I want to do the following:

    • Build foo.c and bar.c and write their object files to make_example/build;
    • Copy foo.h and bar.h to make_example/include;
    • Build main.c, which includes the headers for foo and bar from make_example/includes, and write its object file to make_example/build;
    • Build an executable named demo from main.o, foo.o, and bar.o and save it to the make_example/package directory.

    IOW, after my build is done, I want the following:

    make_example/
      Makefile
      build/
        bar.o
        foo.o
        main.o
      include/
        bar.h
        foo.h
      package/
        demo
      src/
        main.c
        subdir.mk
        Makefile
        subdir1/
          Makefile
          foo.c
          foo.h
        subdir2/
          Makefile
          bar.c
          bar.h
    

    I have a Makefile at the top level directory and each of my source directories. Here's the top-level Makefile (make_example/Makefile):

    CC=gcc
    CFLAGS=-std=c11 -pedantic -Wall -Werror
    
    TARGET=package/demo
    SUBDIRS=src
    
    all: $(TARGET)
    
    # 
    # Makes sure we build all source subdirectories before
    # attempting to build the target
    #
    $(TARGET) : $(SUBDIRS)
        $(CC) -o $@ $(CFLAGS) $(wildcard build/*.o)
    
    #
    # Executes the Makefile in each source subdirectory
    # with the current goal (all or clean)
    #
    $(SUBDIRS):
        $(MAKE) -C $@ $(MAKECMDGOALS)
    
    clean: $(SUBDIRS)
        rm -rf $(TARGET)
    
    .PHONY: $(SUBDIRS) all clean
    

    Here's the Makefile for the top-level source directory (make_example/src/Makefile):

    CC=gcc
    CFLAGS=-std=c11 -pedantic -Wall -Werror -g 
    
    TOPDIR=..
    BLDDIR=$(TOPDIR)/build
    INCDIR=$(TOPDIR)/include
    
    #
    # Look for foo.h and bar.h in the ../include
    # subdirectory.  
    #
    CFLAGS += -I $(INCDIR)
    
    SUBDIRS=$(wildcard subdir*/.)
    
    SRCS=main.c
    OBJS=$(SRCS:%.c=$(BLDDIR)/%.o)
    
    TARGET=main
    
    #
    # Required for the $(OBJS) target - expands
    # all of the $$ expressions
    #
    .SECONDEXPANSION:
    
    all: $(TARGET)
    
    #
    # Makes sure the subdirectories are
    # built before attempting to build our
    # target
    #
    $(TARGET) : $(SUBDIRS) $(OBJS)
    
    #
    # Executes the Makefile in each of the source
    # subdirectories with the current goal (all or clean)
    #
    $(SUBDIRS):
        $(MAKE) -C $@ $(MAKECMDGOALS)
    
    $(OBJS): $$(patsubst $(BLDDIR)/%.o, %.c, $$@)
        $(CC) -c -o $@ $(CFLAGS) $<
    
    clean: $(SUBDIRS) 
        rm -rf $(OBJS)
    
    .PHONY: all $(TARGET) $(SUBDIRS)
    

    Since the Makefile for each of subdir1 and subdir2 is identical except for the file names, I created the subdir.mk file for the stuff that's common between the two:

    CC=gcc
    CFLAGS=-std=c11 -pedantic -Wall -Werror -g
    
    TOPDIR=../..
    BLDDIR=$(TOPDIR)/build
    INCDIR=$(TOPDIR)/include
    
    OBJS=$(SRCS:%.c=$(BLDDIR)/%.o)
    HDRS=$(SRCS:%.c=$(INCDIR)/%.h)
    
    all: $(OBJS) $(HDRS)
    
    clean:
        rm -rf $(OBJS) $(HDRS)
    
    #
    # Required for the $(OBJS) and $(HDRS) targets - expands
    # all of the $$ expressions
    #
    .SECONDEXPANSION:
    
    $(OBJS) : $$(patsubst $(BLDDIR)/%.o,%.c,$$@)
        $(CC) -c -o $@ $(CFLAGS) $<
    
    #
    # Copy the header files to the ../../include
    # directory
    #
    $(HDRS) : $$(patsubst $(INCDIR)/%.h,%.h,$$@)
        cp $< $@
    
    .PHONY: all clean
    

    Then each of the make_example/src/subdir* Makefiles is

    SRCS=foo.c
    include ../subdir.mk
    

    and

    SRCS=bar.c
    include ../subdir.mk
    

    So, at the top level (make_example), we simply type

    make
    

    which gives us

    $ make
    make -C src 
    make[1]: Entering directory '/home/john/Development/make_example/src'
    make -C subdir2/. 
    make[2]: Entering directory '/home/john/Development/make_example/src/subdir2'
    gcc -c -o ../../build/bar.o -std=c11 -pedantic -Wall -Werror -g bar.c
    cp bar.h ../../include/bar.h
    make[2]: Leaving directory '/home/john/Development/make_example/src/subdir2'
    make -C subdir1/. 
    make[2]: Entering directory '/home/john/Development/make_example/src/subdir1'
    gcc -c -o ../../build/foo.o -std=c11 -pedantic -Wall -Werror -g foo.c
    cp foo.h ../../include/foo.h
    make[2]: Leaving directory '/home/john/Development/make_example/src/subdir1'
    gcc -c -o ../build/main.o -std=c11 -pedantic -Wall -Werror -g  -I ../include main.c
    make[1]: Leaving directory '/home/john/Development/make_example/src'
    gcc -o package/demo -std=c11 -pedantic -Wall -Werror build/foo.o build/main.o build/bar.o
    

    And now we have all our build artifacts where we want them:

    $ ls -l */*
    -rw-rw-r-- 1 john john  5744 Oct 23 16:13 build/bar.o
    -rw-rw-r-- 1 john john  5744 Oct 23 16:13 build/foo.o
    -rw-rw-r-- 1 john john  5984 Oct 23 16:13 build/main.o
    -rw-rw-r-- 1 john john    56 Oct 23 16:13 include/bar.h
    -rw-rw-r-- 1 john john    56 Oct 23 16:13 include/foo.h
    -rwxrwxr-x 1 john john 21752 Oct 23 16:13 package/demo
    -rw-rw-r-- 1 john john   165 Oct 23 12:54 src/main.c
    -rw-rw-r-- 1 john john   555 Oct 23 15:50 src/Makefile
    -rw-rw-r-- 1 john john   481 Oct 23 15:46 src/subdir.mk
    
    src/subdir1:
    total 12
    -rw-rw-r-- 1 john john 89 Oct 23 12:29 foo.c
    -rw-rw-r-- 1 john john 56 Oct 23 12:07 foo.h
    -rw-rw-r-- 1 john john 32 Oct 23 15:16 Makefile
    
    src/subdir2:
    total 12
    -rw-rw-r-- 1 john john 89 Oct 23 15:03 bar.c
    -rw-rw-r-- 1 john john 56 Oct 23 14:49 bar.h
    -rw-rw-r-- 1 john john 32 Oct 23 15:17 Makefile
    

    And our program runs as

    $ ./package/demo 
    calling foo: In function foo
    calling bar: In function bar
    

    To clean up, all I have to do is type make clean:

    $ make clean
    make -C src clean
    make[1]: Entering directory '/home/john/Development/make_example/src'
    make -C subdir2/. clean
    make[2]: Entering directory '/home/john/Development/make_example/src/subdir2'
    rm -rf ../../build/bar.o ../../include/bar.h
    make[2]: Leaving directory '/home/john/Development/make_example/src/subdir2'
    make -C subdir1/. clean
    make[2]: Entering directory '/home/john/Development/make_example/src/subdir1'
    rm -rf ../../build/foo.o ../../include/foo.h
    make[2]: Leaving directory '/home/john/Development/make_example/src/subdir1'
    rm -rf ../build/main.o
    make[1]: Leaving directory '/home/john/Development/make_example/src'
    rm -rf package/demo
    

    and all our build artifacts are gone:

    $ ls -l */*
    -rw-rw-r-- 1 john john  165 Oct 23 12:54 src/main.c
    -rw-rw-r-- 1 john john  555 Oct 23 15:50 src/Makefile
    -rw-rw-r-- 1 john john  481 Oct 23 15:46 src/subdir.mk
    
    src/subdir1:
    total 12
    -rw-rw-r-- 1 john john 89 Oct 23 12:29 foo.c
    -rw-rw-r-- 1 john john 56 Oct 23 12:07 foo.h
    -rw-rw-r-- 1 john john 32 Oct 23 15:16 Makefile
    
    src/subdir2:
    total 12
    -rw-rw-r-- 1 john john 89 Oct 23 15:03 bar.c
    -rw-rw-r-- 1 john john 56 Oct 23 14:49 bar.h
    -rw-rw-r-- 1 john john 32 Oct 23 15:17 Makefile