C语言之没有main函数的helloworld示例

几乎所有程序员的第一堂课都是学习helloworld程序,下面我们先来重温一下经典的C语言helloworl

代码如下:

/* hello.c */ 
#include <stdio.h> 
 
int main() 

    printf("hello world!\n"); 
    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!\n"); 
    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()去清理内存

好不容易去掉了main函数,这时又发现必须得有一个_start函数,是不是让人很烦,其实_start函数只是一个默认入口,我们是可以指定入口的

代码如下:

/* hello_nomain.c */ 
#include <stdio.h> 
#include <stdlib.h> 
 
int nomain() 

    printf("hello world!\n"); 
    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,甚至可以自己指定程序的入口

(0)

相关推荐

  • 6个变态的C语言Hello World程序

    下面的六个程序片段主要完成这些事情: 输出Hello, World 混乱C语言的源代码 下面的所有程序都可以在GCC下编译通过,只有最后一个需要动用C++的编译器g++才能编程通过. hello1.c #define _________ } #define ________ putchar #define _______ main #define _(a) ________(a); #define ______ _______(){ #define __ ______ _(0x48)_(0x65

  • C语言菜鸟基础教程之Hello World

    首先,需要一款C语言的编译器,可以使用在线编译器,也可以在本地安装编译器,比如Mac电脑可以安装Xcode,PC可以安装Dev C++. 编写第一个程序:Hello World! #include <stdio.h> int main() { /* 我的第一个 C 程序 */ printf("Hello, World! \n"); return 0; } 运行结果: Hello, World! 程序分析: (1) 这里stdio.h是一个头文件.代表stantard inp

  • C语言之没有main函数的helloworld示例

    几乎所有程序员的第一堂课都是学习helloworld程序,下面我们先来重温一下经典的C语言helloworl 复制代码 代码如下: /* hello.c */  #include <stdio.h>    int main()  {      printf("hello world!\n");      return 0;  } 这是一个简单得不能再单的程序,但它包含有一个程序最重要的部分,那就是我们在几乎所有代码中都能看到的main函数,我们编译成可执行文件并查看符号表,

  • C语言中main函数与命令行参数详细讲解

    目录 一.main 函数的概念 二.main 函数的本质 命令行 三.main 函数的参数 四.main 函数一定是程序执行的第一个函数吗 五.小结 一.main 函数的概念 C语言中 main 函数称之为主函数 一个C程序是从 main 函数开始执行的 二.main 函数的本质 main 函数是操作系统调用的函数 操作系统总是将 main 函数作为应用程序的开始 操作系统将 main 函数的返回值作为程序的退出状态 下面看一下 main 函数的返回值: A.c: #include <stdio

  • 详细聊一聊为什么Python没有main函数

    目录 前言 为什么必须强制main函数作为入口? 总结 前言 众所周知,Python中没有所谓的main函数,但是网上经常有文章提到“ Python的main函数”和“建议编写main函数”. 其实,可能他们是想模仿真正的main函数,但是许多人都被误导(或误解),然后编写了非常笨拙的代码. 在开始讨论之前,我们先来回答以下两个问题: 所谓的“main函数”究竟是什么意思? 为什么有些编程语言必须编写main函数? 一些编程语言将main函数作为程序的执行入口,比如C/C++.C#.Java.G

  • C语言main函数的参数及其返回值详细解析

    返回值的作用 main函数的返回值用于说明程序的退出状态.如果返回0,则代表程序正常退出:返回其它数字的含义则由系统决定.通常,返回非零代表程序异常退出.下面我们在winxp环境下做一个小实验.首先编译下面的程序:int main( void ){    return 0;}然后打开附件里的"命令提示符",在命令行里运行刚才编译好的可执行文件,然后输入"echo%ERRORLEVEL%",回车,就可以看到程序的返回值为0.假设刚才编译好的文件是a.exe,如果输入&

  • C语言 main 函数详情

    目录 1.main() 2.void main() 3.int main(void) 4.int main () 5.int main(int argc,char *argv[]) 前言: 但凡是学过C语言的人,都知道要先写main函数,然而很多时候我们看到的main函数却各有差异,这究竟是为啥?哪种是对的呢?今天我们就来聊聊main函数.那么main函数一共有多少个版本呢? 1.main() 直接的mian()并没有返回值,没有入参.这种写法实际来说,部分编译器会显示警告,并且会返回默认值为i

  • C语言sizeof与字符串处理与动态内存分配及main函数参数详解

    目录 常用的字符串处理函数(标准库里面的) sizeof 求字节运算符 动态内存分配函数 main的参数问题 常用的字符串处理函数(标准库里面的) strlen : 用来求一个字符串的长度 #include <string.h>                     size_t strlen(const char *s);                @s : 指向要计算的那个字符串的首地址                     “const char *” : 表示在函数调用过程中,

  • C语言main函数的三种形式实例详解

    在C语言中,main()函数有三种形式. 1.无参数 #include <stdio.h> int main(void) { printf("Hello World!\n"); return 0; } 2.有两个参数 习惯上第一个参数是整型argc,保存了外部调用命令的参数个数,第二个参数是指针数组或二级指针argv,以字符串形式保存了与argc对应的参数,如下例子: #include <stdio.h> int main(int argc, char* arg

  • c语言main函数使用及其参数介绍

    每一C程序都必须有一main()函数,可以根据自己的爱好把它放在程序的某个地方.有些程序员把它放在最前面,而另一些程序员把它放在最后面,无论放在哪个地方,以下几点说明都是适合的. 在Turbo C2.0启动过程中,传递main()函数三个参数:argc,argv和env.* argc:整数,为传给main()的命令行参数个数.* argv:字符串数组.char* argv[],我们可以看出,argv的类型是char* [],即是一个指向字符数组的指针,所以我们还可以写作:char** argv.

  • Go语言使用select{}阻塞main函数介绍

    很多时候我们需要让main函数不退出,让它在后台一直执行,例如: func main() { for i := 0; i < 20; i++ { //启动20个协程处理消息队列中的消息 c := consumer.New() go c.Start() } select {} // 阻塞 } 可能大多数人想到阻塞的方法是用channel,当然都是可以的,不过用select{}更加简洁 :) 补充:由浅入深聊聊Golang中select的实现机制 正文 话说今天在玩select的时候发现一个问题,是

  • java中main函数你知道多少

    目录 前言 main介绍 main可以被重载吗 main可以被继承吗 main可以被调用吗 总结 前言 前段时间看到一道面试题:“main函数可以被重载么?”,当时就蒙圈了,怎么还会有这种面试题,现在面试官都这么变态么?虽然变态不过还是挺感兴趣的,于是就复习了一遍基础题 [此处坏笑表情] main介绍 我们开始学习java程序的时候,最先拍得一段代码肯定是main函数,main函数的格式如下: public static void main(String[] args){} 首先针对 main

随机推荐