先看一段程序

#include <stdio.h>
void test()
{
    printf("Hello Word!\n");
    return 0;
}

没有main函数,编译一定不会通过,在gcc下编译会提示以下信息:

/usr/lib/gcc/i686-linux-gnu/4.7/../../../i386-linux-gnu/crt1.o:在函数‘_start’中:
(.text+0x18):对‘main’未定义的引用
collect2: 错误: ld 返回 1

可以看到错误信息提示,提到了一个“crt1.o”这个文件,其中crt是“C runtime library”的缩写,其含义是“C运时库”。

C运行时库除了给我们提供必要的库函数调用(如memcpy、printf、malloc等)之外,它提供的另一个最重要的功能是为应用程序添加启动函数。C运行时库启动函数的主要功能为进行程序的初始化,对全局变量进行赋初值,加载用户程序的入口函数。

从给出的错误提示信息中还可以得知,在crt1.o中,有一个名为“_start”的函数。现在网上找到一份crt1.o的伪代码:

section .text:
    __start:
    
     :
     init stack;
     init heap;
     open stdin;
     open stdout;
     open stderr;
     :
     push argv;
     push argc;
     call _main; (调用 main)
     :
     destory heap;
     close stdin;
     close stdout;
     close stderr;
     :
     call __exit;

从伪代码可以看出,在这个_start函数中,调用了main函数。那么我们可不可以用什么手段,去修改入口函数,把入口函数改成test函数呢?

gcc test.c -etest -nostartfiles

其中-e选项为修改函数的入口地址,可惜在网上和man手册里都没有找到有关-e这个选项的解释,只搜索到了-E这个选项。问了好多人才明白原来-e指的 就是entrance。这里把entrance指定为test函数。

-nostartfiles选项的作用是通知编译器不自动加入启动函数以及别的库级 别的初始化,这样就不会调用到crt1.o中的_start函数。

此时,编译通过。可是执行程序,可以成功打印出Hello Word!字样,但是却会提示“段错误”。

Hello Word!

段错误

原来,在编译的时候加上了-nostartfiles这个选项的同时,使得最后的return语句不能正常执行。解决方案是,把程序最后的return语句改成exit语句,让exit语句来做最后的“善后工作”。

#include <stdio.h>
void test()
{
    printf("Hello Word\n");
    exit();
}

没有任何错误。

还有两种方法。。。

方法一:

define预处理指令
这种方式很简单,只是简单地将main字符串用宏来代替,或者使用##拼接字符串。示例程序如下:
#include <stdio.h>
#define start main
int start()
{
    printf("Hello Word\n");
    ;
}
#include <stdio.h>
#define start m##a##i##n
int start()
{
    printf("Hello Word\n");
    ;
}

方法二:

_start函数
_start函数是C程序的入口函数,会调用main函数。在调用main函数之前,会先执行_start函数分配必要的资源,然后再调用main函数。但是在用gcc编译程序时可以使用-nostartfiles选项来重写_start函数。示例程序如下:
#include <stdio.h>
#include <stdlib.h> 

_start(void)
{
    printf("Hello Word\n");
    exit();
}

编译指令:

gcc -nostartfiles _start.c -o main.out

反汇编生成的执行程序

a.

Disassembly of section .plt:

 <puts@plt-0x10>:
: ff  ea    pushq  <_GLOBAL_OFFSET_TABLE_+0x8>
: ff  ec    jmpq * <_GLOBAL_OFFSET_TABLE_+0x10>
40032c: 0f 1f   nopl 0x0(%rax)

 <puts@plt>:
: ff  ea    jmpq * <_GLOBAL_OFFSET_TABLE_+0x18>
:      pushq $0x0
40033b: e9 e0 ff ff ff jmpq  <puts@plt-0x10>

 <exit@plt>:
: ff  e2    jmpq * <_GLOBAL_OFFSET_TABLE_+0x20>
:      pushq $0x1
40034b: e9 d0 ff ff ff jmpq  <puts@plt-0x10>

Disassembly of section .text:

 <_start>:
:  push %rbp
:   e5 mov %rsp,%rbp
: bf     mov $0x400368,%edi
: e8 d2 ff ff ff callq  <puts@plt>
40035e: bf     mov $0x0,%edi
: e8 d8 ff ff ff callq  exit@plt
上面的结果是完整的反汇编结果,我们可以看到_start函数中只有我们调用printf和exit函数相关的一些指令,并且.txt段中只有_start函数,没有看到main函数。如果将源代码中的_start替换为main,重新编译程序,反汇编的结果中会看到_start函数会调用到main。
另外还有一点需要注意,因为这里重写了_start函数,所以gcc为默认的main函数准备的清理动作就没用上,所以如果退出的时候直接使用return,会导致程序崩溃。所以这里要使用exit()来退出程序。
原因请参看这篇文章:http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html

不以main为入口的函数的更多相关文章

  1. 探秘varian:优雅的发布部署程序

    上一篇文章<记一次诡异的故障排查经历>中有介绍到我们的部署程序varian,文章发布后有小伙伴对varian很感兴趣,今天就简单的介绍一下我们的varian,揭开她神秘的面纱~ 什么是va ...

  2. Qt Windows下链接子系统与入口函数(终结版)(可同时存在main和WinMain函数)

    Qt Windows下链接子系统与入口函数(终结版) 转载自:http://blog.csdn.net/dbzhang800/article/details/6358996 能力所限,本讨论仅局限于M ...

  3. C++改变编程入口为main函数

    1, 你用vc建了一个控制台程序,它的入口函数应该是main, 而你使用了WinMain. 2.  你用vc打开了一个.c/.cpp 文件,然后直接编译这个文件,这个文件中使用了WinMian而不是m ...

  4. java main函数不执行?

    今天脑袋短路,对于这个问题纠结了好久.这个问题具体是这样的: public class test { public static void main(String[] args) { test2 t ...

  5. Java面向对象 Main函数 静态的应用 单例设计模式

     Java面向对象 Main函数 静态的应用与单例设计模式 知识概要             (1)Main函数的细解 (2)静态的应用,静态变量,静态代码块,静态函数 (3)单例设计模式 1.M ...

  6. 通过启动函数定位main()函数

      如下,通过vc6.0编写一个hello world程序.尝试结合汇编代码.分析启动函数找到main函数.   在printf(xxx)插入断点,调试执行.如下,在堆栈窗口中可见main()下的一个 ...

  7. java里面main函数为什么要用static修饰

    这学期刚开java,因为之前只写过C++和Python没接触过java,有些写法挺不习惯的,今天写完一个程序,run的时候发现提示the selection can't be launched.... ...

  8. 程序入口函数和glibc及C++全局构造和析构

    分类: CRT Machnasim 2011-06-15 17:45 144人阅读 评论(0) 收藏 举报 c++汇编linuxlist语言编译器 1,程序入口函数和初始化 操作系统在装载可执行文件后 ...

  9. 重新认识Java中的程序入口即主函数各组成部分

    主函数各组成部分深入理解 public static void main(String[] agrs) 主函数:是一个特殊的函数,作为程序的入口,可以被JVM调用 主函数的定义: public:代表着 ...

随机推荐

  1. 吴恩达深度学习笔记(deeplearning.ai)之循环神经网络(RNN)(三)

    1. 导读 本节内容介绍普通RNN的弊端,从而引入各种变体RNN,主要讲述GRU与LSTM的工作原理. 事先声明,本人采用ng在课堂上所使用的符号系统,与某些学术文献上的命名有所不同,不过核心思想都是 ...

  2. web实现负载均衡的几种实现方式

    摘要: 负载均衡(Load Balance)是集群技术(Cluster)的一种应用.负载均衡可以将工作任务分摊到多个处理单元,从而提高并发处理能力.目前最常见的负载均衡应用是Web负载均衡.根据实现的 ...

  3. Github 上传代码的两种方式

    上传本地代码/文件->Github 折腾了半天时间... Github前期准备部分 1)登录github,新建一个 repository 2)repository 命名 3)Github是一个托 ...

  4. Ubuntu下的MongoDB GUI 可视化管理工具

    目录 1 Robo 3T 2 NoSQLBooster for MongoDB(收费) 3 JetBrains Plugin Repository :: Mongo Plugin Ubuntu下的Mo ...

  5. linux中readl()和writel()函数---用于读写寄存器

    writel() 往内存映射的 I/O 空间上写数据,wirtel() I/O 上写入 32 位数据 (4字节). 原型: #include <asm/io.h> void writel ...

  6. SpringMVC 原理和流程

    请求到来时,第一个接受这个请求的前端控制器叫DispatcherServlet(这个需要在web.xml中配置),后端控制器叫Controller. 简化版流程: 1.spring mvc将所有的请求 ...

  7. HDU 6425 Rikka with Badminton(组合问题签到)题解

    题意:问你有多少种选法使得不能满足大于等于2个拍子且大于等于1个球. 思路:数学组合问题,分类讨论一下,刚开始的时候分的很乱,写的乱七八糟的...还有注意MOD,基本上有大数相乘的地方都要先MOD一下 ...

  8. Springboot与日志

    日志框架 比如开发一个大型系统:1.System.out.println(""):将关键数据打印在控制台:去掉?写在一个文件?2.框架来记录系统的一些运行时信息:日志框架 :riz ...

  9. Python学习札记(二十六) 函数式编程7 修饰器

    修饰器 NOTE 1.函数对象有一个__name__属性,可以拿到函数的名字: #!/usr/bin/env python3 def now(): print('2017/2/19') def mai ...

  10. MaintainableCSS 《可维护性 CSS》 --- 模板篇

    什么是模块(Modules) ? 模块是一个特别的独立单元,可以与其他模块组合以形成更复杂的结构. 在客厅里,我们可以认为电视,沙发和墙艺术是模块.它们聚在一起创造一个可用的房间. 如果我们把其中一个 ...