Proper way to include C code from directories other than the current directory

核能气质少年 提交于 2020-01-05 05:37:13

问题


I have two directories, sorting and searching (children of the same directory), that have .c source files and .h header files:

mbp:c $ ls sorting
array_tools.c       bubble_sort.c       insertion_sort.c    main            selection_sort.c
array_tools.h       bubble_sort.h       insertion_sort.h    main.c          selection_sort.h

mbp:c $ ls searching
array_tools.c   array_tools.h   binary_search.c binary_search.h linear_search.c linear_search.h main        main.c

Within searching, I am building an executable that needs to use insertion_sort function, declared in insertion_sort.h and defined in insertion_sort.c inside sorting. The following compilation successfully produces an executable:

mbp:searching $ clang -Wall -pedantic -g -iquote"../sorting" -o main main.c array_tools.c binary_search.c linear_search.c ../sorting/insertion_sort.c

However, I would like to be able to include functions from arbitrary directories by including a header using #include and then providing the compiler with the search path. Do I need to precompile the .c files to .o files beforehand? The man page for clang lists the following option:

-I<directory>
      Add the specified directory to the search path for include files.

But the following compilation fails:

mbp:searching $ clang -Wall -pedantic -g -I../sorting -o main main.c array_tools.c binary_search.c linear_search.c
Undefined symbols for architecture x86_64:
  "_insertion_sort", referenced from:
      _main in main-1a1af0.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

main.c has the following includes:

#include <stdio.h>          
#include <stdlib.h>         

#include "linear_search.h"  
#include "binary_search.h"  
#include "array_tools.h"    
#include "insertion_sort.h" 

I do not understand the link between header files, source files, and object files. To include a function defined in a .c file, is it sufficient to include the homonymous header file, given that the .c file is in the same directory as the header? I have read multiple answers here on SO, the man page for clang and a number of tutorials, but was unable to find a definitive, clear answer.


In response to @spectras:

One by one, you give the compiler a source file to work on. For instance:

cc -Wall -Ipath/to/some/headers foo.c -o foo.o

Running

mbp:sorting $ clang -Wall insertion_sort.c -o insertion_sort.o

produces the following error:

Undefined symbols for architecture x86_64:
  "_main", referenced from:
     implicit entry/start for main executable
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

回答1:


Okay, it's mixed up a bit. Let's see how one typically compiles a simple multi-file project.

One by one, you give the compiler a source file to work on. For instance:

cc -c -Wall -Ipath/to/some/headers foo.c -o foo.o

The -c flag tells the compiler you want an object file, so it should not run the linker.

  • The compiler runs the preprocessor on the source file. Among other things, every time it sees a #include directive, it searches the include paths for named file and basically copy-pastes it, replacing the #include with the content. This is done recursively.

    This is the step where all .h you include get merged into the source file. We call the whole thing a translation unit.

    You can see the result of this step by using -E flag and inspect the result, for instance:

    cc -Wall -Ipath/to/some/headers foo.c -E -o foo.test
    
  • Let's make this short as other steps are not relevant to your question. The compiler then creates an object file from the resulting source code. The object file contains binary version of all code and data that was in the translation unit, plus metadata that will be used to put everything together and some other stuff (like debugging info).

    You can inspect the contents of an object file using objdump -xd foo.o.

Note that as this is done for each source file, this means that headers get parsed and compiled again and again and again. That's the reason they should only declare stuff and not contain actual code: you would end up with that code in every object file.

Once done, you link all the object files into an executable, for instance:

cc foo.o bar.o baz.o -o myprogram

This step will gather all, resolve dependencies and write everything into an executable binary. You may also pull in external object files using -l, like when you do -lrt or -lm.

For instance:

  • foo.c includes bar.h
  • bar.h contains a declaration of function do_bar: void do_bar(int);
  • foo.c can use it, and compiler will generate foo.o correctly
  • foo.o will have placeholders and the information that it requires do_bar
  • bar.c defines the implementation of do_bar.
  • so bar.o will have the information “hey if anyone needs do_bar, I got it here”.
  • linking step will replace placeholders with actual calls to do_bar.

Finally, when you pass multiple .c files to the compiled like you do in your question, the compiler does basically the same thing, only it won't generate the intermediate object files. Overall process behaves the same though.

So, what about your error?

Undefined symbols for architecture x86_64:
  "_insertion_sort", referenced from:
      _main in main-1a1af0.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

See? It says linking step failed. That means previous step went well. The #include worked. It's just in the linking step, it's looking for a symbol (data or code) called _insertion_sort, and does not find it. That's because that symbol was declared somewhere (otherwise source using it would not have compiled), but its definition is not available. Either no source file implemented it, or the object file that contains it was not given to the linker.

=> You need to make _insertion_sort's definition available. Either by adding ../sorting/insertion_sort.c to the source lists you pass or by compiling it into an object file and passing that. Or by building it into a library so it can be shared by your two binaries (otherwise they'll each have a copy embedded).

When you get there, usually starting to use a build toolsuite such as CMake is a good idea. It will take care of all the details for you.



来源:https://stackoverflow.com/questions/45684384/proper-way-to-include-c-code-from-directories-other-than-the-current-directory

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!