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" ?
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:
foo.c
and bar.c
and write their object files to make_example/build
;foo.h
and bar.h
to make_example/include
;main.c
, which includes the headers for foo
and bar
from make_example/includes
, and write its object file to make_example/build
;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