研究C语言的hello world输出

研究C语言的hello world输出

环境准备 gcc make strace

bash 复制代码
$ gcc --version
gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
bash 复制代码
$ make --version
GNU Make 4.3
Built for x86_64-pc-linux-gnu
Copyright (C) 1988-2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
bash 复制代码
$ strace --version
strace -- version 6.8
Copyright (c) 1991-2024 The strace developers <https://strace.io>.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Optional features enabled: stack-trace=libunwind stack-demangle m32-mpers mx32-mpers

C 代码

c 复制代码
#include <stdio.h>

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

GCC 编译的4个阶段 预处理|编译|汇编|链接

预处理

输入: .c 源文件

输出: .i 文件

bash 复制代码
$ gcc -E hello.c -o hello.i

-E : 仅执行预处理,不进行编译,汇编,链接

-o : 指定输出文件名

  1. 递归展开头文件 stdio.h等
  2. 替换宏定义
  3. 处理条件编译
  4. 删除所有注释
bash 复制代码
$ file hello.c
hello.c: C source, ASCII text

$ file hello.i
hello.i: C source, ASCII text

编译

输入: .i 文件 (或者 .c文件)

输出: .s 汇编文件

-S : 仅执行预处理和汇编,不进行汇编和链接

1.此法分析: 将源代码拆分为记号(token)

2.语法分析:构建语法树, 检查语法错误

3.语义分析:检查类型匹配,变量作用域等

4.中间代码生成与优化:生成与机器无关的中间表示

5.目标汇编代码生成

bash 复制代码
$ gcc -S hello.i -o hello.s
bash 复制代码
$ file hello.s
hello.s: assembler source, ASCII text

汇编

输入:.s 汇编文件 (或者直接输入.c .s文件)

输出:.o 目标文件(二进制格式,如ELF)

-c : 执行预处理|编译|汇编,但不进行链接

bash 复制代码
$ gcc -c hello.s -o hello.o
``

```bash
$ file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

链接

输入:.o 文件+库文件(libc.a libc.so

输出:.out 可执行文件 或共享库

bash 复制代码
$ gcc hello.o -o hello
bash 复制代码
$ file hello
hello: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=eacfacba3c3778df9ac2474ebe9b79dc0cabd865, for GNU/Linux 3.2.0, not stripped

使用 make 编译

bash 复制代码
$ rm hello
$ make hello
cc   hello.o   -o hello

使用 ldd(List Dynamic Dependencies) 查看链接了那些库

bash 复制代码
$ ldd hello
	linux-vdso.so.1 (0x00007b5cc46b4000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007b5cc4400000)
	/lib64/ld-linux-x86-64.so.2 (0x00007b5cc46b6000)

查看程序运行中的系统调用

bash 复制代码
$ strace -t -f -o strace_hello.log ./hello 
hello world
bash 复制代码
$ cat strace_hello.log 
10162 22:39:37 execve("./hello", ["./hello"], 0x7ffe0f84c8b0 /* 67 vars */) = 0
10162 22:39:37 brk(NULL)                = 0x5e2c5ac62000
10162 22:39:37 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ab26b3cd000
10162 22:39:37 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
10162 22:39:37 openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
10162 22:39:37 fstat(3, {st_mode=S_IFREG|0644, st_size=60167, ...}) = 0
10162 22:39:37 mmap(NULL, 60167, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7ab26b3be000
10162 22:39:37 close(3)                 = 0
10162 22:39:37 openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
10162 22:39:37 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220\243\2\0\0\0\0\0"..., 832) = 832
10162 22:39:37 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
10162 22:39:37 fstat(3, {st_mode=S_IFREG|0755, st_size=2125328, ...}) = 0
10162 22:39:37 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
10162 22:39:37 mmap(NULL, 2170256, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7ab26b000000
10162 22:39:37 mmap(0x7ab26b028000, 1605632, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7ab26b028000
10162 22:39:37 mmap(0x7ab26b1b0000, 323584, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b0000) = 0x7ab26b1b0000
10162 22:39:37 mmap(0x7ab26b1ff000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1fe000) = 0x7ab26b1ff000
10162 22:39:37 mmap(0x7ab26b205000, 52624, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7ab26b205000
10162 22:39:37 close(3)                 = 0
10162 22:39:37 mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ab26b3bb000
10162 22:39:37 arch_prctl(ARCH_SET_FS, 0x7ab26b3bb740) = 0
10162 22:39:37 set_tid_address(0x7ab26b3bba10) = 10162
10162 22:39:37 set_robust_list(0x7ab26b3bba20, 24) = 0
10162 22:39:37 rseq(0x7ab26b3bc060, 0x20, 0, 0x53053053) = 0
10162 22:39:37 mprotect(0x7ab26b1ff000, 16384, PROT_READ) = 0
10162 22:39:37 mprotect(0x5e2c2ad4b000, 4096, PROT_READ) = 0
10162 22:39:37 mprotect(0x7ab26b40d000, 8192, PROT_READ) = 0
10162 22:39:37 prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
10162 22:39:37 munmap(0x7ab26b3be000, 60167) = 0
10162 22:39:37 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x1), ...}) = 0
10162 22:39:37 getrandom("\x5c\x64\x6b\x57\xf2\xe3\x94\x6a", 8, GRND_NONBLOCK) = 8
10162 22:39:37 brk(NULL)                = 0x5e2c5ac62000
10162 22:39:37 brk(0x5e2c5ac83000)      = 0x5e2c5ac83000
10162 22:39:37 write(1, "hello world\n", 12) = 12
10162 22:39:37 exit_group(0)            = ?
10162 22:39:37 +++ exited with 0 +++

第一列:进程或者线程ID

第二列:时间戳 -tt 会显示微秒

第三列:系统调用名称以及参数

"="号之后 : 系统调用的返回值

text 复制代码
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3

和之前 ldd 查看的一致

主要运行的系统调用

text 复制代码
10162 22:39:37 write(1, "hello world\n", 12) = 12

提取第3列的系统调用

bash 复制代码
$ awk '{print $3}' strace_hello.log | sed 's/(.*//' > syscalls_c
$ cat syscalls_c
execve
brk
mmap
access
openat
fstat
mmap
close
openat
read
pread64
fstat
pread64
mmap
mmap
mmap
mmap
mmap
close
mmap
arch_prctl
set_tid_address
set_robust_list
rseq
mprotect
mprotect
mprotect
prlimit64
munmap
fstat
getrandom
brk
brk
write
exit_group
+++

python 代码

python 复制代码
print("hello world")

查看 python 的运行的系统调用

bash 复制代码
$ strace -t -f -o strace_hello_py.log python3 hello.py 
hello world

python 输出的系统调用非常多

bash 复制代码
$ wc -l strace_hello_py.log 
392 strace_hello_py.log

而C语言的非常少

bash 复制代码
$ wc -l strace_hello.log 
36 strace_hello.log

python 输入hello world 也执行了 write 系统调用

text 复制代码
$ cat strace_hello_py.log | grep write
11529 23:06:07 write(1, "hello world\n", 12) = 12

发现和之前C语言输出的系统调用一致的

text 复制代码
进程 11529 在 23:06:07 时刻,向标准输出(文件描述符1)写入12字节的内容(hello world + 换行),内核成功完成写入并返回12,表示全部写入成功。这是程序正常输出信息的典型系统调用

参数1 1 文件描述符。在Unix/Linux中,0=标准输入,1=标准输出,2=标准错误。此处 1 表示写入标准输出(通常是终端或控制台)。

参数2 "hello world\n" 要写入的数据内容,这里是一个字符串,包含 hello world 和一个换行符 \n。

参数3 12 要写入的字节数。字符串 "hello world\n" 长度为12字符(11个字母+1个空格+1个换行)。

返回值 12 write 系统调用实际写入的字节数。成功时返回等于请求写入的字节数(12),表示全部写入成功。如果返回 -1 则表示出错,并设置 errno。

相关推荐
小小19921 小时前
vue 单页面请求
开发语言·前端·javascript
hhb_6181 小时前
JavaScript 本地存储与动态数据渲染实战案例
开发语言·javascript·ecmascript
淀粉肠kk1 小时前
【C++11】智能指针详解
开发语言·c++
kyriewen111 小时前
Next.js部署:从本地跑得欢,到线上飞得稳
开发语言·前端·javascript·科技·react.js·前端框架·ecmascript
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题】【Java基础篇】第21题:HashMap和Hashtable的区别是什么
java·开发语言·面试·哈希算法·散列表·hash table
不想写代码的星星1 小时前
COW(Copy-on-Write):开抄开抄,哎嘿,我装的
开发语言·c++
慕容卡卡1 小时前
Claude 使用神器(web页面)--CloudCLI UI
java·开发语言·前端·人工智能·ui·spring cloud
咬_咬1 小时前
go语言学习(函数)
开发语言·学习·golang
froginwe111 小时前
PHP MySQL Delete 操作指南
开发语言