?!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
如题Q把CE序中的d数int main(void)Ҏstatic int main(void)会怎么样呢Q?/p>
比如?/p>
复制代码
#include <stdio.h>
int main(void)
{
printf("Hi\n");
return 0;
}
复制代码
修改为:
复制代码
#include <stdio.h>
static int main(void)
{
printf("Hi\n");
return 0;
}
复制代码
误者先自己想一惻I
————————————————————分割线——————————————————?/p>
q个问题是我在看static关键字的时候提出来的?/p>
只要你了解static关键字会使标C符h内部链接QInternel LinkageQ属性,q且了解qCE序的编译链接流E,应该可以得出{案—?/p>
把CE序中的d数int main(void)Ҏstatic int main(void)会导致链接失败?/p>
可以验证一下:
[zhanghaiba@Fedora code]$ gcc static_int_main.c
/usr/lib/gcc/i686-redhat-linux/4.4.5/../../../crt1.o: In function `_start':
(.text+0x18): undefined reference to `main'
collect2: ld returned 1 exit status
如果换成gcc -c呢?
[zhanghaiba@Fedora code]$ gcc -c static_int_main.c
[zhanghaiba@Fedora code]$
可见换成gcc -c可以~译成功Q因为gcc -c只有预处理、编译和汇编阶段Q没有链接阶Dc?/p>
首先Q我们要了解一下Linux下GCC环境中CE序的编译链接流E—?/p>
~译CE序Q一般包括了C预处理阶DcC到汇~的~译阶段、汇~到目标文g的编译阶Dc目标文件的链接阶段?/p>
GCC支持下面几个命oQ我们可以观察到这些阶D:
1Qgcc -v GCC.c
~译时打印出ȝ~译程Q可以看C用了哪些~译工具。v是verboseQ冗长)的意思,卛_可能多的打印信息?/p>
2) gcc -E GCC.c
把源文g用预处理器处理,可重定向输出到GCC.i文g再查?/p>
3Qgcc -S GCC.c
把源文g用预处理器和~译器处理,自动输出同名的GCC.s文g
4Qgcc -c GCC.c
把源文g用预处理器、编译器和汇~器处理Q自动输出同?o文g
5Qgcc GCC.c
把源文g用预处理器、编译器、汇~器处理后,最后用链接器生成~省名ؓa.out的可执行文g
Z么默认叫a.outQ因为早期编译ƈ没有链接器的概念Qa.out是汇~器直接生成的,a意ؓassembly。但需要澄清的是在C~译器中a.out都是由链接器生成?/p>
另外Q用选项-save-temps可以保留中间生成的文ӞC如下Q?nbsp;
[zhanghaiba@Fedora code]$ ls | grep hi
hi.c
hi.i
hi.o
hi.s
我们再用gcc -v来观察ȝ~译程
复制代码
[zhanghaiba@Fedora code]$ gcc -v hi.c
Using built-in specs.
Target: i686-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-1.5.0.0/jre --enable-libgcj-multifile --enable-java-maintainer-mode --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --disable-libjava-multilib --with-ppl --with-cloog --with-tune=generic --with-arch=i686 --build=i686-redhat-linux
Thread model: posix
gcc version 4.4.5 20101112 (Red Hat 4.4.5-2) (GCC)
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=i686'
/usr/libexec/gcc/i686-redhat-linux/4.4.5/cc1 -quiet -v hi.c -quiet -dumpbase hi.c -mtune=generic -march=i686 -auxbase hi -version -o /tmp/ccrwAICf.s
ignoring nonexistent directory "/usr/lib/gcc/i686-redhat-linux/4.4.5/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/i686-redhat-linux/4.4.5/../../../../i686-redhat-linux/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/local/include
/usr/lib/gcc/i686-redhat-linux/4.4.5/include
/usr/include
End of search list.
GNU C (GCC) version 4.4.5 20101112 (Red Hat 4.4.5-2) (i686-redhat-linux)
compiled by GNU C version 4.4.5 20101112 (Red Hat 4.4.5-2), GMP version 4.3.1, MPFR version 2.4.2.
GGC heuristics: --param ggc-min-expand=81 --param ggc-min-heapsize=95788
Compiler executable checksum: e892644090a9a7e8c330a388c51818dd
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=i686'
as -V -Qy -o /tmp/cc1w7Hxi.o /tmp/ccrwAICf.s
GNU assembler version 2.20.51.0.2 (i686-redhat-linux) using BFD version version 2.20.51.0.2-15.fc13 20091009
COMPILER_PATH=/usr/libexec/gcc/i686-redhat-linux/4.4.5/:/usr/libexec/gcc/i686-redhat-linux/4.4.5/:/usr/libexec/gcc/i686-redhat-linux/:/usr/lib/gcc/i686-redhat-linux/4.4.5/:/usr/lib/gcc/i686-redhat-linux/:/usr/libexec/gcc/i686-redhat-linux/4.4.5/:/usr/libexec/gcc/i686-redhat-linux/:/usr/lib/gcc/i686-redhat-linux/4.4.5/:/usr/lib/gcc/i686-redhat-linux/
LIBRARY_PATH=/usr/lib/gcc/i686-redhat-linux/4.4.5/:/usr/lib/gcc/i686-redhat-linux/4.4.5/:/usr/lib/gcc/i686-redhat-linux/4.4.5/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=i686'
/usr/libexec/gcc/i686-redhat-linux/4.4.5/collect2 --no-add-needed --eh-frame-hdr --build-id -m elf_i386 --hash-style=gnu -dynamic-linker /lib/ld-linux.so.2 /usr/lib/gcc/i686-redhat-linux/4.4.5/../../../crt1.o /usr/lib/gcc/i686-redhat-linux/4.4.5/../../../crti.o /usr/lib/gcc/i686-redhat-linux/4.4.5/crtbegin.o -L/usr/lib/gcc/i686-redhat-linux/4.4.5 -L/usr/lib/gcc/i686-redhat-linux/4.4.5 -L/usr/lib/gcc/i686-redhat-linux/4.4.5/../../.. /tmp/cc1w7Hxi.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i686-redhat-linux/4.4.5/crtend.o /usr/lib/gcc/i686-redhat-linux/4.4.5/../../../crtn.o
复制代码
注意U色加粗部分Q由l色文g生成U色文gQ—?/p>
Q?Qcc1是GCC~译环境中的C~译器,把C代码~译为汇~代码,输出?s文g
Q?Qas是汇~器Q把汇编代码~译为目标文Ӟ输出?o文g
Q?Qcollect2是GCC后期版本使用的链接器Q环境)Q其实是先调用GNU的链接器ld对目标文件进行链接,最后收集与E序初始化相关的信息Q构造程序的初始化结构?/p>
ld是真正的链接器,对上一步的.o目标文g和其它需?o文g或静态链接库.a文g、动态链接库.so文gQ如解压C标准库libc.a中取出需要的printf.o文gQ,一起链接输Zؓa.out文g?/p>
GCC后期版本使用了collect2来作为链接器Q其实是间接调用ld链接器?/p>
上面用到的工具中Qas是GNU自带的汇~器Qld是GNU自带的链接器Q它俩是GNU Binutils中最主要的二q制工具?/p>
其中Qld-linux.so.2是动态链接器。最后注?lc参数Ql表示链接Qc表示标准C库,?libc.a或libc.so?/p>
让我们回到问题本w—?/p>
main不是C语言的关键字Q但却是U定俗成的主函数名字Q不q它q不是程序执行的入口Q?/p>
CE序真正入口是_start全局W号Q由汇编实现的函敎ͼQ_start函数会调用库函数__libc_start_mainQ然后__libc_start_main再调用main函数
我们知道main函数的声明无非两UŞ式,main函数的声明(mainW号Q其实是在crt1.o目标文g?/p>
通过nm工具可以查看crt1.o包括了哪些符?/p>
复制代码
[zhanghaiba@Fedora code]$ nm /usr/lib/crt1.o
00000000 R _IO_stdin_used
00000004 D __data_start
U __libc_csu_fini
U __libc_csu_init
U __libc_start_main
00000044 R _fp_hw
00000020 T _start
00000004 W data_start
U main
复制代码
crt1.o中已l有了mainW号Q但却是未定?U)的,所以需要我们来实现main函数Q即定义mainW号Q,最后通过链接器来链接Q这里称作符可析)
如果把main函数定义为staticQ也是h内部链接(Internel Linkage)属性,则编译后的目标文件是局部符P当前文g可见Q?/p>
然而链接是不会对局部符号做W号解析的,只会Ҏ目标文g?rel.textD|指示链接全局的且未定义的W号Q即修改可重定位目标文gREL的符号地址Q?/p>
因此Q链接时mainW号找不到定义,q导致mainW号找不到具体实玎ͼ定义Q,造成链接p|
我们再看~译p|的反馈信?/p>
/usr/lib/gcc/i686-redhat-linux/4.4.5/../../../crt1.o: In function `_start':
(.text+0x18): undefined reference to `main'
collect2: ld returned 1 exit status
׃隄解了—?/p>
在函数_start中,引用了未定义的符号main
collect2外壳Q链接器ldq回1标记退出状态(出错状态)