c程序编译过程:
- 编译过程:源码.c->(预处理)->预处理过的.i源文件->(编译)->汇编文件.S->(汇编)->目标文件.o->(链接)->elf可执行程序。
预处理用预处理器,编译用编译器,汇编用汇编器,链接用链接器,这几个工具再加上其他一些额外的会用到的可用工具,合起来叫编译工具链。gcc就是一个编译工具链。 - 编译目的:将C的源代码转化成.S的汇编代码。
- 编译参数:gcc xx.c -o xx 指定可执行程序的名称。
gcc xx.c -c -o xx.o 指定只编译不链接。
gcc -E xx.c -o xx.i 实现只预处理不编译。
include中<>和""的区别:
- <>专门用来包含系统提供的头文件(就是系统自带的,不是程序员自己写的),""用来包含自己写的头文件;更深层次来说:<>的话C语言编译器只会到系统指定目录(编译器中配置的或者操作系统配置的寻找目录,譬如在ubuntu中是/usr/include目录,编译器还允许用-I来附加指定其他的包含路径)去寻找这个头文件(隐含意思就是不会找当前目录下),如果找不到就会提示这个头文件不存在。
- ""包含的头文件,编译器默认会先在当前目录下寻找相应的头文件,如果没找到然后再到系统指定目录去寻找,如果还没找到则提示文件不存在。
总结+注意:规则虽然允许用双引号来包含系统指定目录,但是一般的使用原则是:如果是系统指定的自带的用<>,如果是自己写的在当前目录下放着用"",如果是自己写的但是集中放在了一起专门存放头文件的目录下将来在编译器中用-I参数来寻找,这种情况下用<>。 - 规则虽然允许用双引号来包含系统指定目录,但是一般的使用原则是:如果是系统指定的自带的用<>,如果是自己写的在当前目录下放着用"",如果是自己写的但是集中放在了一起专门存放头文件的目录下将来在编译器中用-I参数来寻找,这种情况下用<>。
头文件包含的真实含义就是:
- 在#include<xx.h>的那一行,将xx.h这个头文件的内容原地展开替换这一行#include语句。过程在预处理中进行。
源代码里的注释:
- 编译器既然不看注释,那么编译时最好没有注释的。实际上在预处理阶段,预处理器会拿掉程序中所有的注释语句,到了编译器编译阶段程序中其实已经没有注释了。
条件编译:
- #define #undef #ifdef
- 例如:
- #ifdef HAHA //如果定义了 #define HAHA, 则条件成立。
- //程序代码
- #elseif
- //程序代码
- #else
- //程序代
- #endif
- #ifdef :只要定义了就成立,与值没关系。
- #if :#if (条件表达式) 当表达式的结果为真时就成立。
- 例如:
- #define DEBUG
- #ifdef DEBUG
- #define debug(x) printf(x)
- #else
- #define debug(x)
- #endi
这样我们可以在程序中尽情的debug出我们的调试信息了,比如说一个程序有3000行debug信息,我们要在正式发布的时候去掉它,就去掉 #define DEBUG就行了(删除或者使用undef)。这相当于一个开关,相当简洁,又对编译后的程序没有任何影响。
宏:
- 宏定义的解析规则就是:在预处理阶段由预处理器进行替换,这个替换是原封不动的替换。
- 宏定义替换会递归进行,直到替换出来的值本身不再是一个宏为止。
- 一个正确的宏定义式子本身分为3部分:第一部分是#dedine ,第二部分是宏名 ,剩下的所有为第三部分。
- 宏可以带参数,称为带参宏。带参宏的使用和带参函数非常像,但是使用上有一些差异。在定义带参宏时,每一个参数在宏体中引用时都必须加括号,最后整体再加括号,括号缺一不可。
例子:
#define MAX(a, b) (((a)>(b)) ? (a) : (b))
#define SEC_PER_YEAR (365*24*60*60UL) //注意这里的UL的位置。
- 带参宏与函数的区别:
执行时间和方式的不同,宏是在预处理的时候进行替换,函数是在编译的时候进行跳转调用。
宏在替换的时候不会对参数做类型检查。
- 如果想省去调用原地展开,又想用参数检查,就用内联函数。如 inline int max(){}。
自己制作静态链接库并使用:
- 自己制作静态库:
使用gcc -c值编译不链接,生成.o文件(gcc haha.c -o haha.o -c)。
然后使用ar工具进行打包成.a归档文件(ar -rc libhaha.a haha.o)。
- 自己使用:
编译:gcc test.c -o test。 报错:test.c:(.text+0xa): undefined reference to `func1' test.c:(.text+0x1e): undefined reference to `func2'(编译错)
改进编译:gcc test.c -o test -laston(指定了静态库)。报错:/usr/bin/ld: cannot find -laston collect2: error: ld returned 1 exit status(链接错)
再次改进编译:gcc test.c -o test -laston -L. 编译成功。
使用nm可以查看.a文件中有哪些符号。
自己制作动态链接库并使用:
- 自己制作动态库:
使用gcc -c值编译不链接,生成.o文件(gcc haha.c -o haha.o -c -fPIC)。-fPIC意识是位置无关码。
使用gcc 生成.so文件(相当于win下的.dll)(gcc -o libhaha.so haha.o - shared)
- 自己使用:
编译:gcc test.c -o test -lhaha -L. 成功。
执行: ./test ,报错:error while loading shared libraries: libaston.so: cannot open shared object file: No such file or directory.
错误原因:动态链接库运行时需要被加载(运行时环境在执行test程序的时候发现他动态链接了libhaha.so,于是乎会去固定目录尝试加载libhaha.so,如果加载失败则会打印以上错误信息。)
解决方法一:将libaston.so放到固定目录下就可以了,这个固定目录一般是/usr/lib目录。cp libaston.so /usr/lib即可。
解决方法二:将这个目录添加到环境变量中。export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/你的目录/sotest。
ldd命令:
作用是可以在一个使用了共享库的程序执行之前解析出这个程序使用了哪些共享库,并且查看这些共享库是否能被找到,能被解析(决定这个程序是否能正确执行)。
如果提示中没有 not found ,就可以正常运行。