(目录)
gcc用于编译c语言程序,g++用于编译c++语言程序,两者在使用上基本一样。
一、gcc的编译过程
# 1. 预处理(头文件处理,实际是调用cpp命令)
# 如果加上 -v 选项可以看到gcc到哪些路径下去找头文件
gcc -E main.c -o main.i
# 2. 编译(生成汇编代码)
gcc -S main.i -o main.s
#-------------------------------------------
# 3. 汇编(生成二进制代码,实际是调用as命令)
gcc -c main.s -o main.o
# 查看.o文件的代码段.text和代码段的反汇编结果
objdump -s -d main.o
# 显示文件中重定位入口
objdump -r main.o
#-------------------------------------------
# 4. 链接(把库链接进来,实际是调用ld命令)
gcc main.o -o main
二、常用的环境变量
-
头文件搜索目录 c头文件搜索目录的环境变量:C_INCLUDE_PATH c++头文件搜索目录的环境变量:CPLUS_INCLUDE_PATH
-
库文件搜索目录 静态库目录的环境变量:LIBRARY_PATH 动态库目录的环境变量:LD_LIBRARY_PATH
三、常用参数
- -L 指出链接库所在目录——库文件的集合。 系统默认位置,比如:/usr/lib,/usr/local/lib
- -l(小写L) 在链接库所在目录——库文件的集合——中指出具体的库。且优先链接动态库。 系统默认搜索库的目录,比如 /usr/lib 或者 /usr/local/lib 命名规则:-lNAME 对应 libNAME.a 或者 libNAME.so
- -I(大写i) 指出头文件.h的目录位置。 系统默认位置,比如:/usr/include,/usr/local/include
- -std 指定c或者c++的标准,比如-std=c99,-std=c++11
# 使用ANSI C标准编译,但使用GNU的库来链接。
gcc -Wall -ansi -D_GNU_SOURCE main.c -o main
# 严格按照ANSI C标准执行编译和链接
gcc -Wall -ansi -pedantic main.c -o main
- -g 编译为Debug版本
四、生成库
1. 静态库
# cr就是create and replace
ar cr libNAME.a file1.o file2.o file3.o ... filen.o
# 查看库文件中有多少目标文件.o
ar t libNAME.a
# 生成func1.o和func2.o
gcc -Wall -c func1.c
gcc -Wall -c func2.c
# 生成静态库文件libfunc.a
ar cr libfunc.a func1.o func2.o
# main.c + libfunc.a + (main.c include func.h)
gcc -Wall main.c libfunc.a -o main # 指定依赖项的编译方式,需讲究参数的次序
# 或者
gcc -Wall main.c -lfunc -L. -o main # 头文件在当前目录——#include "func.h"在当前目录下找到了func.h,所以无需用参数-I.来指定头文件查找目录。
# 又或者
export LIBRARY_PATH="$(pwd)" # 编译器会到环境变量LIBRARY_PATH指定的目录中去查找静态库
gcc -Wall main.c -lfunc -o main
可以用如下命令反汇编看下链接静态库中的函数情况:
objdump -DC main > main.txt
2. 动态库
# 依赖项是.o文件时最终的可执行文件执行会报错:Segmentation fault
gcc -Wall -shared -fPIC math.c -o libmath.so # 注意依赖项是.c
gcc -Wall main.c -lmath -L. -I. -o main
# Linux默认只会在系统目录,比如 /usr/local/lib中找.so动态链接库。如果不在系统默认的目录下,需要借助于下边的环境变量来指出.so的路径。
export LD_LIBRARY_PATH="$(pwd)"
./main
# 查看可执行文件连接了哪些动态库
ldd main
# 查看ELF结构
readelf -a libfunc.so
# 查看GOT(Global Offset Table)
readelf -S ./math.so
默认动态库的搜索路径一般在如下的配置文件中定义:
/etc/ld.so.conf
在上述文件中又包含了一个目录:
include /etc/ld.so.conf.d/*.conf
五、解决嵌套注释
#if 0
/*...*/
//...
...
#endif
六、为编译预处理传递参数
#include <stdio.h>
int main() {
#ifdef TEST
printf("section 1\n");
#endif
printf("section 2\n");
return 0;
}
向上边程序的预处理语句传递参数
$ gcc test.c -o test -DTEST
$ ./test
section 1
section 2
$ gcc test.c -o test
$ ./test
section 2
查看gcc预定义的宏(cpp指c processor program)
cpp -dM /dev/null
再看一个gcc向代码传递参数的例子
#include <stdio.h>
int main() {
printf("Outside param: %d\n", OK);
return 0;
}
命令
$ gcc test.c -o test -DOK=10
$ ./test
Outside param: 10
七、程序崩溃与core文件
在程序崩溃的时候会产生core dump文件。
$ ulimit -c
0 ---> 0说明操作系统不允许产生core dump文件
$ ulimit -c unlimited ---> 可以使用 ulimit -a 查看所有限制
$ ulimit -c
unlimited ---> 允许产生core dump文件
配置/etc/security/limits.conf文件才可永久修改ulimit
比如修改文件如下:
-----------------------------------------------------------
* soft core unlimited
* hard core unlimited
-----------------------------------------------------------
$ ./test
Segmentation fault (core dumped)
$ gdb test core.2343
当程序发生段错误时,操作系统会生成一个名为 core 或 core.<进程ID> 的核心转储文件,其中包含了程序崩溃时的内存映像和其他相关信息。这个core文件通常会被转储到当前工作目录下,但也可能不会。如果不在当前目录下生成core文件,可以使用如下命令查看在哪里生成了core文件。
$ sudo cat /proc/sys/kernel/core_pattern
core.test.790
^ ^
1 2
1: 崩溃的程序名
2: 进程号
修改 core 文件的生成路径可以使用命令: sysctl -w kernel.core_pattern= <path_to_directory>/core
$ sudo sysctl -w kernel.core_pattern=core 设置
$ sudo sysctl -p 保存
$ sudo sysctl kernel.core_pattern 查看
上述命令执行完后便可在当前目录下生成core dump文件了。 然后,可以使用gdb结合core文件,调试问题程序。
$ gcc -g test.c -o test
$ ./test
Segmentation fault (core dumped)
$ gdb ./test /mnt/wslg/dumps/core.test.879
...
Program terminated with signal SIGSEGV, Segmentation fault.
#0 main () at test.c:6
6 printf("ret: %d\n", *get()); <--- 这里显示程序崩溃的位置
八、编译优化
1. CSE(Common Subexpression Elimination)
将源代码中表达式的计算结果用变量缓存,后边有使用需求时,直接向缓存取值即可。
2. FL(Function Inlining)
比如:内联函数inline。用于消除函数的调用。
3. Loop Unrolling
取消循环的判断
for(int i=0; i<5; i++) a[i]=i;
优化为:
a[0]=0;
a[1]=1;
a[2]=2;
a[3]=3;
a[4]=4;
for(int i=0; i<n; i++) a[i]=i;
优化为:
for(int i=0; i<(n%2); i++) a[i]=i;
for(; i+1<n; i+=2) {
a[i]=i;
a[i+1]=i+1;
}
4. Scheduling
CPU对待执行指令的调度。
5. GCC优化参数
-O[0, 1, 2, 3] 其中 -O0 表示不优化。
九、查看可执行文件
file main
main: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=5d86c79d72ef6e05481dc88b7679975924d7dfe9, for GNU/Linux 3.2.0, not stripped
其中:
- ELF(Executable and Linking Format) ELF是Linux下的可执行文件格式(COFF,Common Object File Format,Windows下的可执行文件格式)。
- LSB(Least Significant Byte) 低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。还可能是MSB(Most Significant Byte)。
- not stripped 包含符号表Symbol Table。命令 nm <可执行文件名> 可以看到符号表。
- Most of the symbols are for internal use by the compiler and operating system. A 'T' in the second column indicates a function that is defined in the object file, while a 'U' indicates a function which is undefined (and should be resolved by linking against another object file).
- The most common use of the nm command is to check whether a library contains the definition of a specific function, by looking for a 'T' entry in the second column against the function name.
- You can get a complete explanation of the output of nm in the GNU Binutils manual.
十、两个优化工具
- gprof 可以查看各调用函数执行的耗时。
gcc -Wall -pg main.c -o main
./main # 在相同目录下生成 gmon.out
gprof main
- gcov 覆盖测试工具。
gcc -Wall -fprofile-arcs -ftest-coverage main.c -o main
在main的同一目录下会生成main.gcno文件
./main
在main的同一目录下又会生成main.gcda
gcov main.c
接着产生main.c.gcov
grep "#####" *.gcov 查看哪条语句未被执行