学习交流加程序员
- 我的qq: 1126137994
- 我的微信: liu1126137994
- 学习交流资源分享qq群: 962535112
GCC (GNU Compiler Collection)编程
gcc 单指GCC中的C语言编译器ubuntu
想了解更多更详细的关于编译连接深层次内容,请阅读书籍《CSAPP》第7章与《程序员的自我修养》,由于这里个人学习记录只记录结果与经常使用的几个编译方法。bash
咱们先来看一个简单的程序:微信
test.c源程序:ide
#include <stdio.h>
#include "func.h"
int g_global = 0;
int g_test = 1;
int main(int argc, char *argv[]) {
func();
printf("&g_global = %p\n", &g_global);
printf("&g_test = %p\n", &g_test);
printf("&func = %p\n", &func);
printf("&main = %p\n", &main);
return 0;
}
复制代码
func.h头文件:工具
#include <stdio.h>
void func() {
#ifdef TEST
printf("TEST = %s\n", TEST);
#endif
return;
}
复制代码
在Linux下使用gcc进行编译:布局
gcc test.c -o test
复制代码
而后运行:学习
./test
复制代码
结果以下:优化
&g_global = 0x804a020
&g_test = 0x804a014
&func = 0x80483c4
&main = 0x80483c9
复制代码
很明显,上述程序很简单,大一的新生都知道为何。可是今天咱们不是学习这个程序的,而是想要了解,运行 gcc test.c -o test 这个命令后,是如何一步一步生成可执行文件test的。
实际上,上述C程序从源文件到二进制可执行文件,有如下四个步骤:
大概编译一个源程序为二进制文件的过程以下图所示:
固然,上面没有列出连接器,在生成file.o后,还须要将file.o与系统的库文件进行连接,生成最终的可执行文件。
从而,咱们就知道了,gcc其实内部包含了预处理器,编译器,汇编器,连接器这四部分。
这四部分这里只是来简单介绍一下(网上一大堆,本文侧重点不在此):
本文的重点来了,上述的内容过于简单,而本节的内容虽然不难,可是并不被大多数人所了解,因此是本文的重点学习记录。
下面将要学习的gcc选项,在工做中具备很强的实用性。
gcc -E file.c -o file.i
复制代码
实用上述编译选项 -E 能够获得预处理后的文件,有时候咱们在程序中定义的宏可能有错误,而这种错误又很难找,此时若是能得预处理后的文件,就能够方便定位错误。
写汇编程序很难,可是若是先写成C语言,再将这个C语言转化成汇编语言,就会很简单。gcc编译工具中,-S选项,能够达到这个目的。好比如下程序: foo.c程序:
#include <stdio.h>
void foo(){
printf("This is foo().\n");
}
复制代码
咱们使用以下命令进行编译:
gcc -S -O2 foo.c -o foo.s
复制代码
将会生成一个foo.c相同做用的汇编程序foo.s,以下:
.file "foo.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "This is foo().\n"
.text
.p2align 4,,15
.globl foo
.type foo, @function
foo:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl $.LC0, 4(%esp)
movl $1, (%esp)
call __printf_chk
leave
ret
.size foo, .-foo
.ident "GCC: (Ubuntu/Linaro 4.4.4-14ubuntu5.1) 4.4.5"
.section .note.GNU-stack,"",@progbits
复制代码
使用-S 参数时,咱们能够根据须要使用-O优化选项。从foo.s的内容能够看出,"This is foo().\n" 这个字符串是放在.rodata段的。看来获取C程序对应的汇编代码,对C语言实现方面的细节,也有所帮助。
gcc -v file.c
复制代码
获取file.c使用的系统头文件的位置
若是咱们想要知道程序中各个符号的内存布局的信息,可使用以下命令:
gcc -Wl,-Map=file.map file.c -o file
复制代码
有时候程序中须要的某一个常量会依赖工做环境的不一样而改变,这个时候,咱们能够将这个常量定义为宏,可是这样,咱们仍是须要每次都在源程序中将宏的值改变,这也很麻烦,此时就能够利用编译选项 -D,在编译的命令行进行宏定义。
还有就是程序中或许会存在下属这样的代码: test.c程序:
#include <stdio.h>
#include "func.h"
int g_global = 0;
int g_test = 1;
int main(int argc, char *argv[]) {
func();
printf("&g_global = %p\n", &g_global);
printf("&g_test = %p\n", &g_test);
printf("&func = %p\n", &func);
printf("&main = %p\n", &main);
return 0;
}
复制代码
test.h头文件:
#include <stdio.h>
void func() {
#ifdef TEST
printf("TEST = %s\n", TEST);
#endif
return;
}
复制代码
在头文件中,有一处定义 # ifdef TEST ....
很明显,上面的两个文件,都没有定义这个TEST,因此程序运行结果以下:
&g_global = 0x804a020
&g_test = 0x804a014
&func = 0x80483c4
&main = 0x80483c9
复制代码
可是可能在某个场合,又必需要使用TEST定义,那么此时,咱们确定不肯意在程序中改来改去,此时就利用编译器的 -D选项,来定义这个TEST。以下编译命令:
gcc -D'TEST="test" ' test.c -o test
复制代码
运行程序后,结果以下:
TEST = test
&g_global = 0x804a020
&g_test = 0x804a014
&func = 0x80483c4
&main = 0x80483e1
复制代码
大多数人应该知道make,若是不知道也没有关系。 在makefile中,make须要经过依赖关系来决定,每次构建时哪些文件须要从新编译。使用gcc的-M选项,能够获得make所须要的源文件的依赖关系。-MM选项可让gcc生成不包含系统文件的依赖关系。
好比有以下源文件: main.c源文件(main.h与foo.c的内容是什么都行)
#include <stdio.h>
#include "main.h"
#include "foo.c"
int main(){
printf("Hello world!\n");
return 0;
}
复制代码
对其进行以下编译
gcc -M main.c
复制代码
将获得以下输出:
能够看到,这句是make所须要的main.c的依赖关系。
若是使用以下命令的话:
gcc -MM main.c
复制代码
将获得以下输出:
当一个可执行程序的生成,须要使用其余库时,须要在连接时加以指定。这就须要用到gcc 的-l与-L选项。
假设一个程序叫作main.c,它编译成可执行程序不光须要系统的标准库,还须要一个库:libfoo.a 且这个libfoo.a与main.c在同一个目录,那么在编译main.c时,须要如下命令:
gcc -o main -L. main.c -lfoo
复制代码
注意:
更加详细的内容参考《程序员的自我修养》
今天学习了gcc的简单概念,与gcc的经常使用的参数选项。