把C程序的int main(void)改成static int main(void)会怎样呢?
如题,把C程序中的主函数int main(void)改成static int main(void)会怎么样呢?
比如把
#include <stdio.h> int main(void)
{
printf("Hi\n");
return 0;
}
修改为:
#include <stdio.h> static int main(void)
{
printf("Hi\n");
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
把C程序的int main(void)改成static int main(void)会怎样呢?的更多相关文章
- Java中的static关键字解析(转自海子)__为什么main方法必须是static的,因为程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问。
Java中的static关键字解析 static关键字是很多朋友在编写代码和阅读代码时碰到的比较难以理解的一个关键字,也是各大公司的面试官喜欢在面试时问到的知识点之一.下面就先讲述一下static关键 ...
- 就因为把int改成Integer,第2天被辞了
本文节选自<设计模式就该这样学>之享元模式(Flyweight Pattern) 1 故事背景 一个程序员就因为改了生产环境上的一个方法参数,把int型改成了Integer类型,因为涉及到 ...
- 一个疏忽损失惨重!就因为把int改成Integer,第2天被辞了
1 故事背景 一个程序员就因为改了生产环境上的一个方法参数,把int型改成了Integer类型,因为涉及到钱,结果上线之后公司损失惨重,程序员被辞退了.信不信继续往下看.先来看一段代码: public ...
- 我的第7个java程序--把java web项目改为java project项目--mybatis
连接数据库需要 程序,连接字符串,查询语句 主程序->读取连接字符串->读取查询语句->把查询到的值赋值给映射对象->打印对象属性 java project的好处,不用做那么多 ...
- (1)定义一个接口Compute含有一个方法int computer(int n,int m); (2)设计四个类分别实现此接口,完成+-*/运算 (3)设计一个类UseCompute,含有方法: public void useCom(Compute com, int one, int two) (4)设计一个测试类
package b; public interface Computer { int computer(int n,int m); } package b; public class Jia impl ...
- 利用接口做参数,写个计算器,能完成+-*/运算 (1)定义一个接口Compute含有一个方法int computer(int n,int m); (2)设计四个类分别实现此接口,完成+-*/运算 (3)设计一个类UseCompute,含有方法: public void useCom(Compute com, int one, int two) 此方法要求能够:1.用传递过来的对象调用compute
package com.homework5; public interface Compute { //声明抽象方法 int computer(int n,int m); } package com. ...
- 38.利用接口做参数,写个计算器,能完成+-*/运算 (1)定义一个接口Compute含有一个方法int computer(int n,int m); (2)设计四个类分别实现此接口,完成+-*/运算 (3)设计一个类UseCompute,含有方法: public void useCom(Compute com, int one, int two) 此方法要求能够:1.用传递过来的对象调用comp
//接口Compute package jieKou; public interface Compute { int Computer(int n,int m); } //加 package jieK ...
- 为什么main方法是public static void?
Main方法是我们学习Java编程语言时知道的第一个方法,你是否曾经想过为什么main方法是public.static.void的.当然,很多人首先学的是C和C++,但是在Java中main方法与前者 ...
- Net Core通用主机项目报错 程序不包含适合于入口点的静态Main
Net Core通用主机的介绍: https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/host/generic-host?view=as ...
随机推荐
- 浏览器对HTTP请求的编码行为
浏览器对请求的URL编码行为 浏览器会对请求的URL中非ASCII码字符进行编码.这里不是指对整个URL进行编码,而是仅仅对非ASCII码字符部分进行编码,详情请看下面的实验记录. 实验一:在URL参 ...
- js事件处理
1.js中常用的事件处理程序(event Handler) onabort 用户终止了页面的加载 onblur 用户离开了对象 onchange 用户修改了对象 onclick 用户点击了对象 one ...
- TensorFlow从1到2(十)带注意力机制的神经网络机器翻译
基本概念 机器翻译和语音识别是最早开展的两项人工智能研究.今天也取得了最显著的商业成果. 早先的机器翻译实际脱胎于电子词典,能力更擅长于词或者短语的翻译.那时候的翻译通常会将一句话打断为一系列的片段, ...
- Day 12 装饰器,开发封闭.
一.什么是装饰器 装饰器本质上就是一个python函数,他可以让其他函数在不需要做任何代码变动的前提下,增加额外的功能,装饰器的返回值也是一个函数对象. 装饰器的应用场景:比如插入日志,性能测试,事务 ...
- SFML从入门到放弃(3) 视角和碰撞检测
SFML从入门到放弃(3) 视角和碰撞检测 视角 window.draw();所画出的对象是在世界坐标的绝对位置. 视角可以选定在窗口中显示世界坐标下的的哪一个区域. sf::View init_vi ...
- MySQL(存储过程,支持事务操作)
day61 保存在MySQL上的一个别名 > 一坨SQL语句 -- delimiter // -- create procedure p1() -- BEGIN -- select * ...
- linux下tomcat运行war包常用命令
一.先是war包copy到 linux 的相关目录,我这的是/opt/soft/tomcat_ecp/webapps. 如果是老项目,在导入war的之前,习惯上是把之前的war备份一下, 如 mv p ...
- (转)C# Enum,Int,String的互相转换 枚举转换--非常实用
Enum为枚举提供基类,其基础类型可以是除 Char 外的任何整型.如果没有显式声明基础类型,则使用 Int32.编程语言通常提供语法来声明由一组已命名的常数和它们的值组成的枚举. 注意:枚举类型的基 ...
- django -orm操作总结
前言 Django框架功能齐全自带数据库操作功能,本文主要介绍Django的ORM框架 到目前为止,当我们的程序涉及到数据库相关操作时,我们一般都会这么搞: 创建数据库,设计表结构和字段 使用 MyS ...
- day 44 django 学习入门
前情提要: 终于学到了Django ...古川小姐姐好流b .....7天学完.....脑壳疼..为了出了这个小火箭.. 详细参考官网. https://www.django.cn/ 中文网站 一: ...