zoukankan      html  css  js  c++  java
  • 没有main函数的helloworld

    差点儿全部程序猿的第一堂课都是学习helloworld程序,以下我们先来重温一下经典的C语言helloworl
    /* hello.c */
    #include <stdio.h>
    
    int main()
    {
        printf("hello world!
    ");
        return 0;
    }

    这是一个简单得不能再单的程序。但它包括有一个程序最重要的部分。那就是我们在差点儿全部代码中都能看到的main函数。我们编译成可运行文件并查看符号表,过滤出里面的函数例如以下(为了方便查看我手动调整了grep的输出的格式,所以和你的输出格式是不一样的)

    $ gcc hello.c -o hello
    $ readelf -s hello | grep FUNC
    Num:    Value          Size Type    Bind   Vis      Ndx Name
    27: 000000000040040c     0 FUNC    LOCAL  DEFAULT   13 call_gmon_start
    32: 0000000000400430     0 FUNC    LOCAL  DEFAULT   13 __do_global_dtors_aux
    35: 00000000004004a0     0 FUNC    LOCAL  DEFAULT   13 frame_dummy
    40: 0000000000400580     0 FUNC    LOCAL  DEFAULT   13 __do_global_ctors_aux
    47: 00000000004004e0     2 FUNC    GLOBAL DEFAULT   13 __libc_csu_fini
    48: 00000000004003e0     0 FUNC    GLOBAL DEFAULT   13 _start
    51: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.2.5
    52: 00000000004005b8     0 FUNC    GLOBAL DEFAULT   14 _fini
    53: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_
    58: 00000000004004f0   137 FUNC    GLOBAL DEFAULT   13 __libc_csu_init
    62: 00000000004004c4    21 FUNC    GLOBAL DEFAULT   13 main
    63: 0000000000400390     0 FUNC    GLOBAL DEFAULT   11 _init

    大家都知道用户的代码是从main函数開始运行的。尽管我们仅仅写了一个main函数,但从上面的函数表能够看到还有其他非常多函数,比方_start函数。实际上程序真正的入口并非main函数,我们以以下命令对hello.c代码进行编译

    $ gcc hello.c -nostdlib
    /usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000400144

    -nostdlib命令是指不链接标准库,报错说找不到entry symbol _start,这里是说找不到入口符号_start,也就是说程序的真正入口是_start函数

    实际上main函数仅仅是用户代码的入口,它会由系统库去调用,在main函数之前,系统库会做一些初始化工作,比方分配全局变量的内存。初始化堆、线程等,当main函数运行完后,会通过exit()函数做一些清理工作。用户能够自己实现_start函数

    /* hello_start.c */
    #include <stdio.h>
    #include <stdlib.h>
    
    _start(void)
    {
        printf("hello world!
    ");
        exit(0);
    }

    执行例如以下编译命令并执行

    $ gcc hello_start.c -nostartfiles -o hello_start
    $ ./hello_start
    hello world!

    这里的-nostartfiles的功能是Do not use the standard system startup files when linking,也就是不使用标准的startup files,可是还是会链接系统库,所以程序还是能够运行的。相同我们查看符号表

    $ readelf -s hello_start | grep FUNC
    Num:    Value          Size Type    Bind   Vis      Ndx Name
    20: 0000000000400350    24 FUNC    GLOBAL DEFAULT   10 _start
    21: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.2.5
    22: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND exit@@GLIBC_2.2.5 

    如今就仅仅剩下三个函数了。而且都是我们自己实现的。当中printf因为仅仅有一个參数会被编译器优化为puts函数。在编译时加-fno-builtin选项能够关掉优化

    假设我们在_start函数中去掉exit(0)语句。程序运行会出core。这是由于_start函数运行完程序就结束了。而我们自己实现的_start里面没有调用exit()去清理内存

    好不easy去掉了main函数。这时又发现必须得有一个_start函数,是不是让人非常烦,事实上_start函数仅仅是一个默认入口,我们是能够指定入口的

    /* hello_nomain.c */
    #include <stdio.h>
    #include <stdlib.h>
    
    int nomain()
    {
        printf("hello world!
    ");
        exit(0);
    }

    採用例如以下命令编译

    $ gcc hello_nomain.c -nostartfiles -e nomain -o hello_nomain

    当中-e选项能够指定程序入口符号,查看符号表例如以下

    $ readelf -s hello_nomain | grep FUNC
    Num:    Value          Size Type    Bind   Vis      Ndx Name
    20: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.2.5
    21: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND exit@@GLIBC_2.2.5
    22: 0000000000400350    24 FUNC    GLOBAL DEFAULT   10 nomain

    对照hello_start的符号表发现仅仅是将_start换成了nomain

    到这里我们就非常清楚了,程序默认的入口是标准库里的_start函数。它会做一些初始化工作,调用用户的main函数。最后再做一些清理工作,我们能够自己写_start函数来覆盖标准库里的_start。甚至能够自己指定程序的入口

  • 相关阅读:
    SAX解析XML笔记
    使用 Angular 2 来创建FlexGrid控件
    算法-快速排序(优雅版)
    使用泛型简化动态代理
    Java泛型概述
    POI-PPT官方文档
    Java 利用POI操作PPT
    Java8学习笔记(九)--日期/时间(Date Time)API指南
    Java8学习笔记(八)--方法引入的补充
    Android WebView加载本地html并实现Java与JS交互
  • 原文地址:https://www.cnblogs.com/gccbuaa/p/6699735.html
Copyright © 2011-2022 走看看