zoukankan      html  css  js  c++  java
  • 把C程序的int main(void)改成static int main(void)会怎样呢?

    如题,把C程序中的主函数int main(void)改成static int main(void)会怎么样呢?

    比如把

    #include <stdio.h>
    
    int main(void)
    {
        printf("Hi
    ");
        return 0;
    }

    修改为:

    #include <stdio.h>
    
    static int main(void)
    {
        printf("Hi
    ");
        return 0;
    }

    请读者先自己想一想!

    ————————————————————分割线———————————————————

    这个问题是我在看static关键字的时候提出来的。

    只要你了解static关键字会使标示符具有内部链接(Internel Linkage)属性,并且了解过C程序的编译链接流程,应该可以得出答案——

    把C程序中的主函数int main(void)改成static int main(void)会导致链接失败。

    可以验证一下:

    [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可以编译成功,因为gcc -c只有预处理、编译和汇编阶段,没有链接阶段。

    首先,我们要了解一下Linux下GCC环境中C程序的编译链接流程——

    编译C程序,一般包括了C预处理阶段、C到汇编的编译阶段、汇编到目标文件的编译阶段、目标文件的链接阶段。

    GCC支持下面几个命令,使我们可以观察到这些阶段:

    1)gcc -v GCC.c

    编译时打印出总的编译流程,可以看到使用了哪些编译工具。v是verbose(冗长)的意思,即尽可能多的打印信息。

    2) gcc -E GCC.c

    把源文件用预处理器处理,可重定向输出到GCC.i文件再查看

    3)gcc -S GCC.c

    把源文件用预处理器和编译器处理,自动输出同名的GCC.s文件

    4)gcc -c GCC.c

    把源文件用预处理器、编译器和汇编器处理,自动输出同名.o文件

    5)gcc GCC.c

    把源文件用预处理器、编译器、汇编器处理后,最后使用链接器生成缺省名为a.out的可执行文件

    为什么默认叫a.out?因为早期编译并没有链接器的概念,a.out是汇编器直接生成的,a意为assembly。但需要澄清的是在现代编译器中a.out都是由链接器生成

    另外,使用选项-save-temps可以保留中间生成的文件,示范如下: 

    [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
     

    注意红色加粗部分(由绿色文件生成红色文件)——

    (1)cc1是GCC编译环境中的C编译器,把C代码编译为汇编代码,输出为.s文件

    (2)as是汇编器,把汇编代码编译为目标文件,输出为.o文件

    (3)collect2是GCC后期版本使用的链接器(环境),其实是先调用GNU的链接器ld对目标文件进行链接,最后收集与程序初始化相关的信息,构造程序的初始化结构

      ld是真正的链接器,对上一步的.o目标文件和其它需要.o文件或静态链接库.a文件、动态链接库.so文件(如解压C标准库libc.a中取出需要的printf.o文件),一起链接输出为a.out文件。

      GCC后期版本使用了collect2来作为链接器,其实是间接调用ld链接器。

    上面用到的工具中,as是GNU自带的汇编器,ld是GNU自带的链接器,它俩是GNU Binutils中最主要的二进制工具。

    其中,ld-linux.so.2是动态链接器。最后注意-lc参数,l表示链接,c表示标准C库,即libc.a或libc.so。

    让我们回到问题本身——

    main不是C语言的关键字,但却是约定俗成的主函数名字,不过它并不是程序执行的入口,

    C程序真正入口是_start全局符号(由汇编实现的函数),_start函数会调用库函数__libc_start_main,然后__libc_start_main再调用main函数

    我们知道main函数的声明无非两种形式,main函数的声明(main符号)其实是在crt1.o目标文件中

    通过nm工具可以查看crt1.o包括了哪些符号

    [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中已经有了main符号,但却是未定义(U)的,所以需要我们来实现main函数(即定义main符号),最后通过链接器来链接(这里称作符号解析)

    如果把main函数定义为static,也就是具有内部链接(Internel Linkage)属性,则编译后的目标文件是局部符号(当前文件可见)

    然而链接是不会对局部符号做符号解析的,只会根据目标文件的.rel.text段来指示链接全局的且未定义的符号(即修改可重定位目标文件REL的符号地址)

    因此,链接时main符号找不到定义,这导致main符号找不到具体实现(定义),造成链接失败

    我们再看编译失败的反馈信息

    /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

    就不难理解了——

    在函数_start中,引用了未定义的符号main

    collect2外壳:链接器ld返回1标记退出状态(出错状态)

    @Author: 张海拔

    @Update: 2014-2-10

    @Link: http://www.cnblogs.com/zhanghaiba/p/3545132.html

  • 相关阅读:
    过河问题 贪心
    喷水装置2 贪心
    喷水装置 贪心算法
    大红数星星 图论 XD网络赛
    Bi-shoe and Phi-shoe 欧拉函数 素数
    c++ 打飞机游戏开发日志
    POJ 1129 Channel Allocation DFS 回溯
    POJ 2676 Sudoku
    LibreOJ #100. 矩阵乘法
    BZOJ 1009: [HNOI2008]GT考试
  • 原文地址:https://www.cnblogs.com/zhanghaiba/p/3545132.html
Copyright © 2011-2022 走看看