GCC小贴士
  0eGysyk4Lrwg 2023年11月02日 27 0
gcc

(目录)


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

二、常用的环境变量

  1. 头文件搜索目录 c头文件搜索目录的环境变量:C_INCLUDE_PATH c++头文件搜索目录的环境变量:CPLUS_INCLUDE_PATH

  2. 库文件搜索目录 静态库目录的环境变量: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 查看哪条语句未被执行
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  0eGysyk4Lrwg   2023年11月02日   28   0   0 gcc
0eGysyk4Lrwg