LV015-多文件编译
一、多文件编程
前边学习函数的时候,我们把函数全部都放在一个文件中,函数少的话倒是无所谓,很多的话,就不是很方便了。 C 语言是支持多文件编程的,我们可以将某些功能放入独立的源文件,形成一个单独的模块,这样不仅便于阅读,也更加方便维护。这也可以称之为 模块化编程。
1. 一个简单的例子
首先来看一个例子,简单了解一下多文件编程时程序的编译和运行吧。
1.1 源码 demo
1.1.1 main.c
#include <stdio.h>
int main(int argc, char *argv[])
{
fun1();
return 0;
}1.1.2 fun1.c
#include <stdio.h>
void fun1(void)
{
printf("fun1\n");
}1.2 编译运行
在终端执行以下命令:
gcc *.c -Wall # 编译程序会在终端看到以下提示信息:
main.c: In function ‘main’:
main.c:5:5: warning: implicit declaration of function ‘fun1’ [-Wimplicit-function-declaration]
fun1();
^~~~虽然有警告,但是依然生成了 a.out 可执行文件,终端运行 ./a.out 命令,看到有如下信息输出:
fun1这说明,虽然有警告,但是依然正常调用了函数。
1.3 小结
通过这个例子,会发现,这样写出来的程序是有警告的,原因就在于 C 语言中函数应该先声明,再调用,这个问题我们下边再解决。
2. extern 关键字
C 语言代码是由上到下依次执行的,不管是变量还是函数,原则上都要先定义再使用,否则就会报错。但在实际开发中,经常会在函数或变量定义之前就使用它们,这个时候就需要提前声明。声明( Declaration ),就是告诉编译器现在要使用这个变量或函数,现在没有找到它的定义不要紧,不要报错,后边会有定义的。
2.1 函数声明
2.1.1 声明格式
在前边学习函数的时候提到过函数声明,那时并没有使用 extern 关键字,因为函数的定义有函数体,函数的声明没有函数体,编译器很容易区分定义和声明,所以对于函数声明来说,有没有 extern 都是一样的。所以函数的声明可以有以下形式,
/* 不使用 extern */
dataType function( dataType arg1, dataType arg2, ... );
dataType function( dataType1, dataType2, ... );
/* 使用 extern */
extern dataType function( dataType1 arg1, dataType2 arg2, ... );
extern dataType function( ddataType1, dataType2, ... );2.1.2 使用示例
- main.c
#include <stdio.h>
extern void fun1(void);
int main(int argc, char *argv[])
{
fun1();
return 0;
}- fun1.c
#include <stdio.h>
void fun1(void)
{
printf("fun1\n");
}- 编译运行
在终端执行以下命令:
gcc *.c -Wall # 编译程序会发现没有警告了,然后在终端运行 ./a.out 命令,看到有如下信息输出:
fun12.2 变量声明
2.2.1 声明格式
变量和函数不同,编译器只能根据 extern 来区分,有 extern 才是声明,没有 extern 就是定义。变量的声明只有一种形式,就是使用 extern 关键字:
extern dataType name;2.2.2 使用示例
- main.c
#include <stdio.h>
extern void fun1(void);
extern int a;
int main(int argc, char *argv[])
{
fun1();
printf("a = %d\n", a);
return 0;
}- fun1.c
#include <stdio.h>
int a = 10;
void fun1(void)
{
printf("fun1 a=%d\n", a);
}- 编译运行
我们执行以下命令:
gcc *.c -Wall # 编译程序
./a.out # 运行可执行程序会看到有如下信息输出:
fun1 a=10
a = 102.2.3 声明的时候初始化
变量不要在声明的同时初始化,格式为:
extern dataType name = value;这种方式会有警告,并且不会正常生成可执行文件。还是上面的程序,我们改成 extern int a = 10; 就会直接产生报错。
main.c:4:12: warning: ‘a’ initialized and declared ‘extern’
extern int a = 10;
^
/tmp/ccy6uPA9.o:(.data+0x0): a'被多次定义
/tmp/ccfIMEHM.o:(.data+0x0):第一次在此定义
collect2: error: ld returned 1 exit status二、怎么多文件编译?
上面我们已经通过一个简单的实例了解了模块化编程,我们会根据功能,模块等将其写在不同的文件中,需要注意的是 一个 C 语言工程中,不管有多少个源文件,都一定只能有一个 main 函数。接下来我们深入了解一下多文件的编译。
1. 在同一个目录
1.1 命令格式
有的时候我们在一个 C 文件中调用了另一个 C 文件中的函数,但是这些文件必须只有一个 main 函数,他们编译的时候要一起编译。一般格式如下:
# 一般我们会在 main 函数所在的源文件目录下编译
# 一起编译链接
gcc file1.c file2.c ... main.c -o file_name
# 分开编译,统一链接
gcc -c file1.c -o file1_name
gcc -c file2.c -o file2_name
...
gcc -c main.c -o main_name
gcc *.o -o file_namefile1.c ... main.c:所有的源文件
file_name :表示最终生成的可执行文件的名称。
1.2 使用实例
这里我们用 LV01_GCC_COMPILE/00_compile/02_multifile 这个实例:
.
├── func1.c
├── func1.h
├── func2.c
├── func2.h
└── main.c我们在 main.c 所在目录进行编译:
gcc func1.c func2.c main.c -o main
发现一切正常。
2. 在不同目录中
2.1 从实例开始
例如:LV01_GCC_COMPILE/00_compile/03_multifile_dir_01 和 LV01_GCC_COMPILE/00_compile/04_multifile_dir_02:
# 自己的 C 文件和自己的头文件一起
.
├── func1
│ ├── func1.c
│ └── func1.h
├── func2
│ ├── func2.c
│ └── func2.h
└── main.c
# C 文件和 h 文件分开
.
├── func
│ ├── func1.c
│ └── func2.c
├── include
│ ├── func1.h
│ └── func2.h
└── main.c其实这两种情况都差不多。
我们按照之前的方式编译:
gcc func1.c func2.c main.c -o main
发现找不到两个源文件,那我们加上路径好了:
gcc ./func1/func1.c ./func2/func2.c main.c -o main
然后又提示找不到头文件。
我们试一下另一个目录的情况:
gcc ./func/func1.c ./func/func2.c main.c -o main
发现也是一样的。
2.2 去哪里找头文件?
上面编译的过程中,我们发现,只要头文件跟 main.c 不在一个目录下,main.c 在编译的时候必然报错,这是在预处理 main.c 的时候找不到这个头文件导致的,我们可以这样:
#include "./include/func1.h"
#include "./include/func2.h"这样修改源文件就不会有问题了,但是这是我们直接写出了头文件的位置,那要是我们优化工程,改变了头文件的位置,那岂不是要把所有的头文件都改一遍?那肯定不行,GCC 为我们提供了 -I 选项,这个选项就指明了头文件的位置。
gcc ./func1/func1.c ./func2/func2.c main.c -o main -I./func1 -I./func2
gcc ./func/func1.c ./func/func2.c main.c -o main -I ./include会发现这样编译就没有任何问题了:

2.3 几个问题
- (1)编译程序时去哪找头文件?
答:系统目录,就是编译工具链里的某个 include 目录;也可以自己指定:编译时用 -I dir 选项指定。 #include <xxx.h> 尖括号中的头文件就是在系统目录找。
- (2)链接时去哪找库文件?
答:系统目录,就是交叉编译工具链里的某个 lib 目录;也可以自己指定:链接时用 -L dir 选项指定。
- (3)运行时去哪找库文件?
答:系统目录,就是板子上的/lib、 /usr/lib 目录;也可以自己指定:运行程序用环境变量 LD_LIBRARY_PATH 指定。
【注意】当我们移植某些功能到 arm 开发板的时候,会有库文件,头文件等。由于运行时不需要头文件,所以头文件不用放到板子上
2.4 系统目录在哪?
我们在编译过程中除了语法问题,可能会遇到:
# 头文件问题
xxx.c:2:10: fatal error: xxxxx.h: 没有那个文件或目录
# 库文件问题
undefined reference to 'xxx'在运行时有可能遇到:
error while loading shared libraries: libxxx.so:
cannot open shared object file: No such file or directory这其实就对应上面的三个问题,他们的答案都是系统目录,那么系统目录在哪? 执行下面命令确定目录:
echo 'main(){}'| gcc -E -v -它会列出头文件目录、库目录(LIBRARY_PATH)。
sumu@sumu-virtual-machine:~/1sharedfiles/linux_develop/imx6ull-app-demo/LV01_GCC_COMPILE/00_compile/multifile_dir_01$ echo 'main(){}'| gcc -E -v -
Using built-in specs.
COLLECT_GCC=gcc
OFFLOAD_TARGET_NAMES=nvptx-none:hsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.4.0-1ubuntu1~20.04.2' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-9QDOt0/gcc-9-9.4.0/debian/tmp-nvptx/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.2)
COLLECT_GCC_OPTIONS='-E' '-v' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/9/cc1 -E -quiet -v -imultiarch x86_64-linux-gnu - -mtune=generic -march=x86-64 -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -fcf-protection
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/gcc/x86_64-linux-gnu/9/include
/usr/local/include
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.
# 1 "<stdin>"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "<stdin>"
main(){}
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/9/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/9/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-E' '-v' '-mtune=generic' '-march=x86-64'后面我们会用到交叉编译工具,就是编译出可以在 arm 板子上跑的程序的工具,我们也来看一下:
sumu@sumu-virtual-machine:~/1sharedfiles/linux_develop/imx6ull-app-demo/LV01_GCC_COMPILE/00_compile/multifile_dir_01$ echo 'main(){}'| arm-linux-gnueabihf-gcc -E -v -
使用内建 specs。
COLLECT_GCC=arm-linux-gnueabihf-gcc
目标:arm-linux-gnueabihf
配置为:/home/tcwg-buildslave/workspace/tcwg-make-release/label/docker-trusty-amd64-tcwg-build/target/arm-linux-gnueabihf/snapshots/gcc-linaro-4.9-2017.01/configure SHELL=/bin/bash --with-mpc=/home/tcwg-buildslave/workspace/tcwg-make-release/label/docker-trusty-amd64-tcwg-build/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu --with-mpfr=/home/tcwg-buildslave/workspace/tcwg-make-release/label/docker-trusty-amd64-tcwg-build/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu --with-gmp=/home/tcwg-buildslave/workspace/tcwg-make-release/label/docker-trusty-amd64-tcwg-build/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu --with-gnu-as --with-gnu-ld --disable-libmudflap --enable-lto --enable-objc-gc --enable-shared --without-included-gettext --enable-nls --disable-sjlj-exceptions --enable-gnu-unique-object --enable-linker-build-id --disable-libstdcxx-pch --enable-c99 --enable-clocale=gnu --enable-libstdcxx-debug --enable-long-long --with-cloog=no --with-ppl=no --with-isl=no --disable-multilib --with-float=hard --with-mode=thumb --with-tune=cortex-a9 --with-arch=armv7-a --with-fpu=vfpv3-d16 --enable-threads=posix --enable-multiarch --enable-libstdcxx-time=yes --with-build-sysroot=/home/tcwg-buildslave/workspace/tcwg-make-release/label/docker-trusty-amd64-tcwg-build/target/arm-linux-gnueabihf/_build/sysroots/arm-linux-gnueabihf --with-sysroot=/home/tcwg-buildslave/workspace/tcwg-make-release/label/docker-trusty-amd64-tcwg-build/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu/arm-linux-gnueabihf/libc --enable-checking=release --disable-bootstrap --enable-languages=c,c++,fortran,lto --build=x86_64-unknown-linux-gnu --host=x86_64-unknown-linux-gnu --target=arm-linux-gnueabihf --prefix=/home/tcwg-buildslave/workspace/tcwg-make-release/label/docker-trusty-amd64-tcwg-build/target/arm-linux-gnueabihf/_build/builds/destdir/x86_64-unknown-linux-gnu
线程模型:posix
gcc 版本 4.9.4 (Linaro GCC 4.9-2017.01)
COLLECT_GCC_OPTIONS='-E' '-v' '-march=armv7-a' '-mtune=cortex-a9' '-mfloat-abi=hard' '-mfpu=vfpv3-d16' '-mthumb' '-mtls-dialect=gnu'
/home/sumu/2software/gcc-linaro-4.9.4/bin/../libexec/gcc/arm-linux-gnueabihf/4.9.4/cc1 -E -quiet -v -imultilib . -imultiarch arm-linux-gnueabihf -iprefix /home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/arm-linux-gnueabihf/4.9.4/ -isysroot /home/sumu/2software/gcc-linaro-4.9.4/bin/../arm-linux-gnueabihf/libc - -march=armv7-a -mtune=cortex-a9 -mfloat-abi=hard -mfpu=vfpv3-d16 -mthumb -mtls-dialect=gnu
忽略重复的目录“/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/../../lib/gcc/arm-linux-gnueabihf/4.9.4/include”
忽略不存在的目录“/home/sumu/2software/gcc-linaro-4.9.4/bin/../arm-linux-gnueabihf/libc/usr/local/include/arm-linux-gnueabihf”
忽略不存在的目录“/home/sumu/2software/gcc-linaro-4.9.4/bin/../arm-linux-gnueabihf/libc/usr/local/include”
忽略重复的目录“/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/../../lib/gcc/arm-linux-gnueabihf/4.9.4/include-fixed”
忽略重复的目录“/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/../../lib/gcc/arm-linux-gnueabihf/4.9.4/../../../../arm-linux-gnueabihf/include”
忽略不存在的目录“/home/sumu/2software/gcc-linaro-4.9.4/bin/../arm-linux-gnueabihf/libc/usr/include/arm-linux-gnueabihf”
#include "..." 搜索从这里开始:
#include <...> 搜索从这里开始:
/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/arm-linux-gnueabihf/4.9.4/include
/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/arm-linux-gnueabihf/4.9.4/include-fixed
/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/arm-linux-gnueabihf/4.9.4/../../../../arm-linux-gnueabihf/include
/home/sumu/2software/gcc-linaro-4.9.4/bin/../arm-linux-gnueabihf/libc/usr/include
搜索列表结束。
# 1 "<stdin>"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "/home/sumu/2software/gcc-linaro-4.9.4/arm-linux-gnueabihf/libc/usr/include/stdc-predef.h" 1 3 4
# 1 "<命令行>" 2
# 1 "<stdin>"
main(){}
COMPILER_PATH=/home/sumu/2software/gcc-linaro-4.9.4/bin/../libexec/gcc/arm-linux-gnueabihf/4.9.4/:/home/sumu/2software/gcc-linaro-4.9.4/bin/../libexec/gcc/arm-linux-gnueabihf/:/home/sumu/2software/gcc-linaro-4.9.4/bin/../libexec/gcc/:/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/arm-linux-gnueabihf/4.9.4/../../../../arm-linux-gnueabihf/bin/
LIBRARY_PATH=/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/arm-linux-gnueabihf/4.9.4/:/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/arm-linux-gnueabihf/:/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/:/home/sumu/2software/gcc-linaro-4.9.4/bin/../lib/gcc/arm-linux-gnueabihf/4.9.4/../../../../arm-linux-gnueabihf/lib/:/home/sumu/2software/gcc-linaro-4.9.4/bin/../arm-linux-gnueabihf/libc/lib/:/home/sumu/2software/gcc-linaro-4.9.4/bin/../arm-linux-gnueabihf/libc/usr/lib/
COLLECT_GCC_OPTIONS='-E' '-v' '-march=armv7-a' '-mtune=cortex-a9' '-mfloat-abi=hard' '-mfpu=vfpv3-d16' '-mthumb' '-mtls-dialect=gnu'