一步到位的hello world


首先一个简单的C语言版本的hello world例子,保存在文件hello.c中。

#include <stdio.h>

int main()
{
printf("hello world\n");
}

一般而言,我们通常可以使用gcc命令将其转化为可执行程序

gcc -o hello hello.c

执行上面命令后,就会在当前目录生产一个hello的可执行文件。在Centos 64位机器上执行file hello,可以得到

hello: ELF 64-bit LSB executable, AMD x86-64, version 1 (SYSV), for GNU/Linux 2.6.9, dynamically linked (uses shared libs), not stripped

直接执行./hello即可在控制台输出hello world。

条分缕析的hello world


为了说明C语言源程序是如何转化为最终的可执行文件,首先看下面这个图



下面来分布讲解

预处理(Preprocessor)阶段

这个阶段处理#开头的指示语句,hello.c中的#include<stdio.h>告知预处理器去加载stdio.h的内容,并把它插入到当前位置。

cpp hello.c > hello.i
file hello.i
# hello.i: ASCII C program text

编译(Compiler)阶段

这个阶段把C语言源程序编译为汇编程序,不同高级语言经由其编译器处理后,得到的同样的汇编语言。

cc -S hello.i   #会生成 hello.s 文件
file hello.s
# hello.s: ASCII assembler program text

组装(Assembly)阶段

这一阶段把汇编语言翻译为机器码,结果保存在称为relocatable object program/file的文件中,以ELF(Executable and Linkable Format)格式存储(包含一个符号表,没有striped过),一般以.o结尾。

as -o hello.o hello.s
file hello.o
# hello.o: ELF 64-bit LSB relocatable, AMD x86-64, version 1 (SYSV), not stripped

链接(Linking)阶段

注意到我们的hello.c程序使用了printf函数,它是由C语言的标准库函数,由C语言编译器提供,printf函数应该会存在于一个printf.o的文件中,我们需要某种手段把它合并到我们的hello.o中,链接器就是做这件事的。最终生成的为一个称为executable object file的文件,它可以被装载进内存并且执行。

# -lc 指定加载libc.a
ld -o hello /usr/lib64/crt*.o hello.o -lc

如果按照上面方式操作,可执行文件hello能够创建出来,但是运行./hello会报错

-bash: ./hello: /lib/ld64.so.1: bad ELF interpreter: No such file or directory

貌似是路径不对,到这里,你可能会想到gcc为什么能够一次成功,gcc是怎么调用ld的呢?我们可以通过-v选项来查看gcc调用ld时的参数

$ gcc -v hello.o -o 123
Using built-in specs.
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared --enable-thre
ads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-libgcj
-multifile --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --disable-plugin --w
ith-java-home=/usr/lib/jvm/java-1.4.2-gcj-1.4.2.0/jre --with-cpu=generic --host=x86_64-redhat-linux
Thread model: posix
gcc version 4.1.2 20080704 (Red Hat 4.1.2-55)
/usr/libexec/gcc/x86_64-redhat-linux/4.1.2/collect2 --eh-frame-hdr -m elf_x86_64 --hash-style=gnu -dynamic-linker /lib64/l
d-linux-x86-64.so.2 -o 123 /usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crt1.o /usr/lib/gcc/x86_64-redhat-linux
/4.1.2/../../../../lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/4.1.2/crtbegin.o -L/usr/lib/gcc/x86_64-redhat-linux/4.1.2
-L/usr/lib/gcc/x86_64-redhat-linux/4.1.2 -L/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64 -L/lib/../lib64 -L/usr/
lib/../lib64 hello.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86
_64-redhat-linux/4.1.2/crtend.o /usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crtn.o

这里重点是collect2这句,因为collect2可以看作ld功能相同的程序,为了方便阅读,我这里手动换了下行

--hash-style=gnu -dynamic-linker /lib64/ld-linux-x86-64.so.2
-o 123
/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crt1.o
/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crti.o
/usr/lib/gcc/x86_64-redhat-linux/4.1.2/crtbegin.o
-L/usr/lib/gcc/x86_64-redhat-linux/4.1.2
-L/usr/lib/gcc/x86_64-redhat-linux/4.1.2
-L/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64
-L/lib/../lib64
-L/usr/lib/../lib64 hello.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed
-lgcc_s --no-as-needed /usr/lib/gcc/x86_64-redhat-linux/4.1.2/crtend.o
/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crtn.o

可以看到,gcc在做链接时传入了这么多参数,至于其中的原因,就比较麻烦了,改日再写一篇文章介绍,今天先到这里。

参考


《深入理解计算机系统》☞hello world背后的故事的更多相关文章

  1. printf背后的故事

    printf背后的故事 说起编程语言,C语言大家再熟悉不过.说起最简单的代码,Helloworld更是众所周知.一条简单的printf语句便可以完成这个简单的功能,可是printf背后到底做了什么事情 ...

  2. Mac OS X 背后的故事

    Mac OS X 背后的故事 作者: 王越  来源: <程序员>  发布时间: 2013-01-22 10:55  阅读: 25840 次  推荐: 49   原文链接   [收藏]   ...

  3. 你好,C++(4)2.1.3 我的父亲母亲:编译器和链接器 2.1.4 C++程序执行背后的故事

    2.1.3  我的父亲母亲:编译器和链接器 从表面上看,我是由Visual Studio创建的,而实际上,真正负责编译源代码创建生成可执行程序HelloWorld.exe的却是Visual Studi ...

  4. elf 文件格式探秘——程序运行背后的故事

    摘要:本文主要讲解elf文件格式,通过readelf命令结合底层的相关数据结构,讲解相关内容,分析程序运行的基本原理. 本文来源:elf 文件格式探秘——程序运行背后的故事 http://blog.c ...

  5. 腾讯技术分享:微信小程序音视频技术背后的故事

    1.引言 微信小程序自2017年1月9日正式对外公布以来,越来越受到关注和重视,小程序上的各种技术体验也越来越丰富.而音视频作为高速移动网络时代下增长最快的应用形式之一,在微信小程序中也当然不能错过. ...

  6. 背后的故事之 - 快乐的Lambda表达式(一)

    快乐的Lambda表达式(二) 自从Lambda随.NET Framework3.5出现在.NET开发者眼前以来,它已经给我们带来了太多的欣喜.它优雅,对开发者更友好,能提高开发效率,天啊!它还有可能 ...

  7. 背后的故事之 - 快乐的Lambda表达式(二)

    快乐的Lambda表达式 上一篇 背后的故事之 - 快乐的Lambda表达式(一)我们由浅入深的分析了一下Lambda表达式.知道了它和委托以及普通方法的区别,并且通过测试对比他们之间的性能,然后我们 ...

  8. ASP.NET Web API自身对CORS的支持: EnableCorsAttribute特性背后的故事

    从编程的角度来讲,ASP.NET Web API针对CORS的实现仅仅涉及到HttpConfiguration的扩展方法EnableCors和EnableCorsAttribute特性.但是整个COR ...

  9. 《深入理解计算机系统V2》学习指导

    <深入理解计算机系统V2>学习指导 目录 图书简况 学习指导 第一章 计算机系统漫游 第二章 信息的表示和处理 第三章 程序的机器级表示 第四章 处理器体系结构 第五章 优化程序性能 第六 ...

  10. DbUtility v3 背后的故事

    DbUtility v3 背后的故事 时间 DbUtility v3构思了差不多大半年,真正开发到第一个版本发布到NuGet却只花了50天.中途大量时间在完善 Jumony 3,只有三周来开发DbUt ...

随机推荐

  1. 3、组件注册-@ComponentScan-自动扫描组件&指定扫描规则

    3.组件注册-@ComponentScan-自动扫描组件&指定扫描规则 3.1 xml方式 benas.xml 导入context命名空间 <?xml version="1.0 ...

  2. [React] Create a Query Parameter Modal Route with React Router

    Routes are some times better served as a modal. If you have a modal (like a login modal) that needs ...

  3. Codeforces Round #572 (Div. 2)

    目录 Contest Info Solutions A. Keanu Reeves B. Number Circle C. Candies! D1. Add on a Tree D2. Add on ...

  4. 五、SSD原理(Single Shot MultiBox Detector)

    主流的算法主要分为两个类型: (1)tow-stage R-CNN系列算法,其主要思路是先通过启发式方法(selective search)或者CNN网络(RPN)产生一些列稀疏的候选框,然后对这些候 ...

  5. Liunx之MySQL安装与主从复制

    MYSQL安装(mariadb) MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可. 开发这个分支的原因之一是:甲骨文公司收购了MySQL后,有将MySQL ...

  6. slax中改变终端字体

    修改~/.Xresources文件 ! English font      Xterm*faceName: DejaVu Sans Mono=True:size=16 修改颜色: ! colos XT ...

  7. Mysql表的横向拆分与纵向拆分

    表的拆分分为横向拆分(记录的拆分)和纵向拆分(字段的拆分).拆分表的目的:提高查询速度. 1.横向拆分 我们从一个案例去解释,情景是这样的:某某博客,有50W的博客量,有2w的用户,发现随着用户和博客 ...

  8. linux下添加动态链接库路径、动态库加载等方法

    linux下添加动态链接库路径的方法 2017年01月20日 10:08:17 阅读数:5596   Linux共享库路径配置 Linux下找不到共享库文件的典型现象为明明已经安装某个软包(如libn ...

  9. 记录linux 生成crash dump文件步骤

    执行文件编译时加入-g 命令 例如 g++ -g test.cpp 查看当前系统限制情况 ulimit -a 设置crash dump 文件大小 ulimit -c unlimited unlimit ...

  10. git如何压栈某一个文件?

    答: 使用git stash -p进行交互式操作,y表示压栈,n表示不压栈