简明Linux-Linux下GCC编译C程序(g++编译c++类似)
GCC编译步骤简介1 GCC编译四步骤2 GCC编译选项3 静态库和共享库3.1 静态库生成及使用3.2 动态库生成及使用3.3**数据段合并和地址回填**1.4 注意事项1 GCC编译四步骤GCC编译可执行程序四步骤:预处理编译汇编链接hello.c>>>>hello.i>>>>>hello.s>>>>>>
1 GCC编译四步骤
GCC编译可执行程序四步骤:预处理 编译 汇编 链接
hello.c>>>>hello.i>>>>>hello.s>>>>>>hello.o
预处理:展开宏/头文件 替换条件编译 删除注释/空行/空白 gcc -E -o hello.i
编译:检查语法规范 汇编语言 gcc -S -o hello.s
汇编:将汇编指令翻译成机器指令 gcc -c hello.s -o hello.o
链接:数据段合并,数据地址回填。将汇编文件变成可执行文件 gcc hello.o -o a.out
四个阶段中,编译阶段消耗时间和系统资源最多;
2 GCC编译选项
在文件夹中含有 hello.h 和 hello.c 头文件,当两者在同一个文件夹中,可以一起编译
gcc hello.c -o main
而在实际工程中,一般会放置多个文件夹,比如源码放在在src中,头文件放入inc文件夹中,编译的时候,就需要指定头文件目录,使用-I
.
├── 123 xyz
├── 1.c
├── 1.tar.gz
├── a.out
├── exe
├── exe1
├── hello.c
├── hello.i
├── hello.s
├── inc
│ └── hello.h
└── test.zip
hello.c的头文件在hello.h中,编译命令是:
gcc hello.c -Iinc -o main
-I和后面参数之间的空格可以灵活调整;
-c 只编译生成.o文件,不进行链接;(只做预处理,编译和汇编),得到的是二进制文件;(可以使用linux 下的file命令查看文件的的类型)
-g 包含调试信息,编译时添加调试文件(加入-g选项,可以进行调试,不加入无法调试);生成的调试文件可以用于gdb调试;
-On n=0~3,编译优化,n越大优化得月多(debug阶段不建议添加优化选项)
-Wall 提示更多警告的信息
-D 编译时定义宏,注意-D和之间没有空格;
具体见下面的代码,代码如果没有宏定义的HELLO,会缺少对HI的定义;而可以通过gcc -D选项,编译动态增加宏定义。用这种方法,可以实现开关功能,比如:
#ifdef SWITCH
printf(“xxxxx”);
#endif
在编译选项中,可以通过是否添加动态宏定义选项,决定是否显示调试信息。
#include <stdio.h>
#ifdef HELLO
#define HI 20
#endif
int main(int argc, char** argv)
{
printf("hi=%d\n",HI);
return 0;
}
20daniel@daniel-Vostro-5471:~/文档/OS/test$ gcc hello_num.c -o a.out
hello_num.c: In function ‘main’:
hello_num.c:7:22: error: ‘HI’ undeclared (first use in this function)
7 | printf("hi=%d\n",HI);
| ^~
hello_num.c:7:22: note: each undeclared identifier is reported only once for each function it appears in
daniel@daniel-Vostro-5471:~/文档/OS/test$ gcc hello_num.c -o a.out -D HELLO
daniel@daniel-Vostro-5471:~/文档/OS/test$ ./a.out
hi=20
daniel@daniel-Vostro-5471:~/文档/OS/test$
-E 生成预处理文件
-M 生成.c文件与头文件依赖关系,以用于Makefile,包括系统库的头文件
-MM 生成.c文件与头文件依赖关系,以用于Makefile,不包括系统库的头文件
-l 用于指定动态库名(动态库和静态库均可使用,比如linux下常用的多线程库,需要添加 -l pthread)
-L 包含指定动态库的路径(动态库和静态库均可使用)
-fPIC 用于将代码生成动态库所需要的ELF文件 gcc -c add.c -o add.o -fPIC
3 静态库和共享库
程序库可以分为静态库(static library)和共享库(shared object,动态库)
静态库,在可执行程序运行之前,就将静态库加入到执行码中,成为程序的一部分;
动态库,是在执行程序启动时动态加载到程序中,可以被多个执行程序共享使用(即多个应用程序可以共享使用该库)。
当多个应用程序都需要使用某个库时,动态库会比静态库节省更多的空间;
静态库:重要用于对程序的空间要求较低,而时间要求较高的核心程序中;
动态库:对时间要求较低,对空间要求更高;
建议库开发人员使用共享库,优点是库独立,便于维护和更新;而静态库更新比较麻烦,一般不推荐。
本节所使用的执行程序和库都是ELF(Executable and Linking Format)格式。
静态库可以认为是一些目标代码的集合,按照习惯,一般以“.a”作为文件后缀,可以使用ar(archiver)命令创建静态库
3.1 静态库生成及使用
ar rcs libmylib.a file1.o rcs是静态库参数,记住。库最好以lib开头,以“.a”结尾。
//add.c
int add(int a, int b)
{
return a+b;
}
//sub.c
int sub(int a, int b)
{
return a-b;
}
例如:
.
├── add.c
├── add.o
├── libcal.a
├── sub.c
└── sub.o
daniel@daniel-Vostro-5471:~/文档/OS/test/static$ gcc -c add.c -o add.o #生成.o文件,只编译不链接
daniel@daniel-Vostro-5471:~/文档/OS/test/static$ gcc -c sub.c -o sub.o #生成.o文件
daniel@daniel-Vostro-5471:~/文档/OS/test/static$ ar rcs libcal.a add.o sub.o #将两个.o文件打包成为一个libcal.a的静态库
daniel@daniel-Vostro-5471:~/文档/OS/test/static$ file libcal.a #调用查看libcal.a的类型
libcal.a: current ar archive
编译出错:会有行号信息,提示出错;
链接出错:不会再有行号信息,提示出错;
collect2:链接器
collect2:error: ld returned 1 exit status (提示了链接出错)
使用静态库的方法,是将主程序和静态库一起编译,例如:
gcc test.c libcal.a -o test #test.c是主程序,libcal.a是上一步生成的静态库;
要把主函数写在库文件之前
也可以先把test.c文件编译成为test.o文件
gcc -c test.c -o test.o #添加-Wall信息,会提示没有声明 add和sub函数
再把test.o和libcal.a相互链接
gcc test.o libcal.a -o main
//test.c文件
//不需要包含add.c和sub.c文件,也可以加进行编译工作,但是会提示错误
#include <stdio.h>
int main(int argc, char** argv)
{
int a=20,b=10;
printf("a+b=%d\n",add(a,b));
printf("a-b=%d\n",sub(a,b));
return 0;
}
在主函数调用其它函数时,首先检查函数定义,如果没有函数定义,会寻找函数声明;如果没有函数声明,编译器会给函数生成隐式定义;
对于上例子,如果没有声明add函数和sub函数,编译器会根据函数名和调用的参数,自动声明为:
int add(int, int);
int sub(int, int);
而对于本例来说,正好正确;在如果隐式声明推测的函数声明和实际声明不同,会导致函数调用失败!!!
正确做法是,给静态库整体做一个头文件,这个头文件有静态库所有函数的声明,然后在主函数中包含这个头文件;头文件内容如下:
#ifndef _CAL_H
#define _CAL_H
int add(int, int);
int sub(int, int);
#endif
标准的目录结构,使用inc存放头文件,使用lib存放静态库,使用src存放源文件,编译命令如下:
daniel@daniel-Vostro-5471:~/文档/OS/test/static$ tree
├── inc
│ └── cal.h
├── lib
│ └── libcal.a
├── main
├── src
│ ├── add.c
│ ├── sub.c
│ └── test.c
└── test.c
3 directories, 7 files
daniel@daniel-Vostro-5471:~/文档/OS/test/static$ gcc test.c ./lib/libcal.a -I./inc -o main
3.2 动态库生成及使用
没有调用到动态库内部的函数的时候,动态库不会加载使用;只有调用到动态库的内部的函数的时候,动态库才会被加载到内存;
1.将 .c 文件生成 .o文件;
动态库要求生成与位置无关的代码(函数调用之前需要将其地址固定)
数据段合并和地址回填 延迟绑定(动态库函数的地址比主函数的其它调用函数分配地址要晚)
结论:制作动态库的.o文件和静态库有区别,生成位置无关文件,借助 -fPIC选项
gcc -c add.c -o add.o -fPIC
2.使用gcc 和 -shared选项制作静态库
gcc -shared -o lib库名.so add.o sub.o
3.编译可执行程序时,指定所使用的动态库, -l和-L
-l:用来指定库名 -L:用来指定库路径
gcc test.c -o a.out -lcal(cal是库名) -L./lib
4.运行可执行程序;
./a.out
示例:文件和之前相同,目录结构如下:
aniel@daniel-Vostro-5471:~/文档/OS/test/dynamic$ tree
├── a.out
├── inc
│ └── cal.h
├── lib
│ ├── libcal.a
│ └── libcal.so
├── main
├── out
├── src
│ ├── add.c
│ ├── add.o
│ ├── sub.c
│ ├── sub.o
│ └── test.c
└── test.c
3 directories, 12 files
gcc -c add.c -o add.o -fPIC
gcc -c sub.c -o sub.o -fPIC
gcc -shared -o lib静态库名.so ../src/add.o ../src/sub.o
#使用下列语句编译,但是编译之后运行程序,终端报错
gcc test.c -l静态库名 -L./lib -Iinc -o main
#上面语句中的静态库名,不包含lib前缀和.so的扩展名,比如
gcc test.c -lcalc -L./lib -I./inc -o main
报错原因是找不到动态文件。
可以通过下面的命令查看运行程序运行之后,需要加载那些动态库,以及动态库的路径
ldd a.out
原因:
链接器:工作于链接阶段,工作时需要 -l和-L选项(用于编译静态库)
动态链接器:工作于程序运行阶段,工作时需要提供动态库所在目录位置;
解决方法:
**1.环境变量是依赖于终端的,切换终端之后,新的终端的环境变量会失效
#环境变量:
LD_LIBRARY_PATH=./lib
#环境变量生效:
export LD_LIBRARY_PATH=./lib
#上面填入的是相对路径,如果要一直生效,最好填入绝对路径
#运行程序
./a.out
**2.一劳永逸的做法是修改bash的配置文件:
(1) 通过gedit或者vi修改bash的配置文件;
#脚本的名字为.bashrc
gedit ~/.bashrc
#或者使用vim打开
vi ~/.bashrc
(2)在终端中添加动态库路径
export LD_LIBRARY_PATH=动态库路径
建议使用绝对路径
(3)使脚本文件生效,通过运行脚本文件实现、
. .bashrc
#或者
source .bashrc
#或者重启终端,每次重启终端,bashrc都会运行
**3.永久解决方法3
拷贝自定义动态库到标准C库(不推荐)
标准C库也是通过动态库进行加载的,而且可以加载成功,这给我们的提示是可以把这个生成的动态库拷贝到C库的文件夹中
sudo cp libmymath.so /lib
然后把原来设置的环境变量删掉。
**4.配置文件法
#打开配置文件
sudo vi /etc/ld.so.conf
在配置文件中写入库文件目录所在位置,最好写入决对目录
让配置文件生效
#-v选项会在终端显示动态库的加载位置
sudo ldconfig -v
运行程序生效
3.3数据段合并和地址回填
32位操作系统的内存使用和分布
4G | 内核区 kernel |
---|---|
3G | stack 栈区 从上往下用 |
0~3G 用户区 user | heap 堆区 堆区从下往上用 |
.bss 初始化为非0的数据区 | |
.data 未初始化数据区 | |
.rodata 只读数据区 | |
0 | .text 代码区 |
栈区从上往下使用,堆区从下往上使用,堆和栈中间的区域随着使用越来越小;
环境变量和main函数的参数,存放在栈底,即靠近3G和4G边缘的位置;
.so 加载位置位于栈区和堆区中间
.text 代码段,是常量,不能被随意改变,属性 ro(只读)
.rodata 只读数据段,存放常量和只读变量,可以和代码段合并
代码段和只读数据段的合并可以节省内存的使用空间,原来.text和.rodata都需要单独以4kB为单位占用内存,合并之后,可以一起以4kB单位占用内存;
4k 内存页的大小,是内存的分割的最小单位
使用磁盘,每个扇区最小512kB
.data和.bss 都是rw,可读可写,也可以进行数据段合并;
3.4 注意事项
1.动态库是否加载到内存,取决于程序运行与否;
2.动态库每次加载的位置不固定;
3.动/静态库共存时,系统默认使用动态库;
更多推荐
所有评论(0)