计算机系统漫游

我们学习一门新的语言, 往往是从Hello world开始的, 我想没有一个程序员不喜欢这句话, 就像一个刚出生的婴儿, 对着这个陌生的世界微笑一样.

信息就是位(bit)加上下文(context)

我们学习一门新的语言, 往往是从Hello world开始的, 我想没有一个程序员不喜欢这句话, 就像一个刚出生的婴儿, 对着这个陌生的世界微笑一样.

这次对计算机系统的学习就从一个hello world程序的生命周期开始. 从他被我们键入编辑器, 到在系统运行, 打印hello world最后interminates.

1
2
3
4
5
#include<stdio.h>
int main(){
printf("hello world\n");
return 0;
}

我们的hello world程序生命的开始是源程序(source progam)或者源文件(source file), 以后缀 .c 结尾, 它是由一系列的0,1代码构成, 我们把每一位叫做bit. 每八个bit组成一个块, 我们称之为一个字节byte. 它是计算机处理信息的最小单位. 每一个byte对应着一个字符.

大多数计算机系统都采用ASCII标准来表示字符, 每一个ASCII码的值都对应着一个唯一的字符. 我们可以在linux或者like-unix系统的终端中键入man ascii来查看对应关系

image-20220928022041369

我们把像hello.c这样用ASCII码表示的文件叫做文本文件(text files), 而其他形式的文件叫做二进制文件(binary files)

在计算机中, 所以的信息都是都是一连串的由0和1组成, 而唯一区分这些信息就是上下文. 在不同上下文中, 相同的一段比特序列可能表示一个整数, 浮点数, 或者一条指令.

程序被编译器翻译成不同的格式

计算机只认识二进制序列, helloworld这样的程序是不能被计算机理解的. 为了让计算机正确的执行我们的指令, 我们必须把它翻译成以二进制表示的形式, 我们把这样由二进制表示的语言叫做机器语言. 这些指令最后被打包成名叫可执行程序的形式, 存储为磁盘中的二进制文件.

在unix系统中, 这个由源文件翻译为可执行文件的过程是由一个叫编译器的程序完成.

整个边缘过程分为4个阶段, 分别是预编译处理, 编译, 汇编, 链接.

  • 预编译阶段修改由#为开始的语句, 比如这段hello.c程序中的#include<stdio.h>, 预编译阶段会在目标路径中找到对应的stdio.h文件, 并直接插入到程序中, 有点内容复制粘贴到过程.

​ 这个stdio.h文件中主要包括函数声明, 宏定义, 以及结构体定义, 这个阶段会生成以.i为后缀的文本文件.

​ 默认情况下,预处理器的输出会被导入到标准输出流(也就是显示器),可以利用-o选项把它导入到某个输出文件, 在shell中键入gcc -E hello.c -o hello.i来输出到hello.i文件中

1
2
3
4
5
6
7
8
extern int __vsnprintf_chk (char * restrict, size_t, int, size_t,
const char * restrict, va_list);
# 400 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/stdio.h" 2 3 4
# 2 "hello.c" 2
int main(){
printf("hello world\n");
return 0;
}

产生的代码很长很长, 大概有500行,这里只截取最后一部分. 可以发现, 即使是想helloworld这样极端简单的程序, 编译之后也会比源文件大很多.

  • 编译阶段把预编译产生的以.i结尾的文件翻译成以汇编语言表示的文本文件, 汇编是一种很有用的低一级的语言, 在不同的编译器编译不同的高级语言中, 它往往作为编译阶段的输出语言.

我们可以在shell中键入gcc -S hello.c生产汇编程序hello.s :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
_main:                                  ; @main
.cfi_startproc
; %bb.0:
sub sp, sp, #32 ; =32
stp x29, x30, [sp, #16] ; 16-byte Folded Spill
add x29, sp, #16 ; =16
.cfi_def_cfa w29, 16
.cfi_offset w30, -8
.cfi_offset w29, -16
mov w8, #0
str w8, [sp, #8] ; 4-byte Folded Spill
stur wzr, [x29, #-4]
adrp x0, l_.str@PAGE
add x0, x0, l_.str@PAGEOFF
bl _printf
ldr w0, [sp, #8] ; 4-byte Folded Reload
ldp x29, x30, [sp, #16] ; 16-byte Folded Reload
add sp, sp, #32 ; =32
ret

  • 汇编阶段

    在汇编阶段编译器会把上一阶段用ASCII表示的文本文件hello.s转化为机器语言表示的指令, 并打包生成叫做可重定位的二进制文件文件, 存储在hello.o的文件中, 如果我们打开这个文件, 文本编辑器会按照ASCII表示成对应的字符, 所以我们将会看到一堆乱码.

  • 链接阶段

注意到我们的helloworld程序调用了一个名为printf的函数, 这个函数是C标准库函数的一部分,由编译起提供. 这个函数保持在一个单独的预编译的目标文件中, 叫做printf.o它必须和我们的hello.o合并, 这个工作由链接器完成, 生成和以直接运行可执行文件hello.

现在, 我们的源程序以及被翻译成了可执行文件, 并存储来磁盘中. 我们可以在shell中键入./hello来运行我们的程序.

MAC M1 安装Oracle数据库教程 纪念拥有了自己的博客
You need to set install_url to use ShareThis. Please set it in _config.yml.

Comments

You forgot to set the shortname for Disqus. Please set it in _config.yml.
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×