程序翻译过程

程序翻译一般分为以下四个步骤:

  1. 预处理:展开头文件、去除注释、宏替换、条件编译等。

  2. 编译:将 C 代码转换为汇编语言。

  3. 汇编:将汇编语言转换为二进制目标文件。

  4. 链接:将用户代码和标准库链接,生成最终可执行文件。

# 预处理
gcc -E test.c -o test.i
# -E:仅执行预处理,生成的文件是test.i
​
# 编译为汇编
gcc -S test.c -o test.s
# -S:编译完成汇编,不继续生成目标文件
​
# 汇编为二进制目标文件
gcc -c test.s -o test.o
# -c:生成目标文件test.o
​
# 链接,生成可执行文件
gcc test.o -o test
# 将目标文件与库进行链接,生成可执行文件test

动态链接与静态链接

在代码中调用库函数时,实际只调用了函数名,函数实现是在链接阶段完成的。链接分为动态链接静态链接两种方式。

动态链接

  • 可执行文件较小,节省内存、磁盘和网络资源。

  • 运行时直接调用动态链接库 .so 文件。

静态链接

  • 在编译时将所需的库代码拷贝到可执行文件中,生成的可执行文件较大。

  • 不依赖外部库版本变化。

查看库的链接类型

使用 file 命令查看可执行文件是否为动态链接:

file test

输出结果示例:

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

示例中 dynamically linked 表明此程序是动态链接的。

使用 ldd 命令查看动态库依赖:

ldd test

输出示例:

linux-vdso.so.1 (0x00007a3c38b04000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007a3c38800000)
libm.so.6 => /usr/lib/libm.so.6 (0x00007a3c38711000)
libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007a3c38a9c000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007a3c38520000)

动态库与静态库的命名规则

  • 动态库:以 .so 为后缀,前缀为 lib,如 libc.so 是 C 标准库。

  • 静态库:以 .a 为后缀,前缀为 lib,如 libm.a 是数学库。

静态链接示例

使用静态库进行链接:

g++ test.cpp -o test -static

使用静态链接生成的可执行文件通常比动态链接的大得多。

Linux 项目自动化构建工具:Make 和 Makefile

在大型项目中,Makefile 用于定义编译规则,指定编译顺序。使用 make 命令可自动化编译,提升开发效率。

Makefile 原理

一个简单的 Makefile 示例:

mycode: mycode.c  # mycode 依赖于 mycode.c
    gcc mycode.c -o mycode

Makefile 中的每一行命令必须以 TAB 键 开头。常用的命令如 .PHONY 表示伪目标,始终执行。

.PHONY: clean  # 伪目标 clean
clean:
    rm -rf mycode  # 清理生成的文件

Makefile 示例:手动实现进度条

Makefile

test: main.cpp test.cpp test.h
    g++ test.cpp main.cpp -o test -DN=3

.PHONY: clean
clean:
    rm -rf test

test.cpp

#include "test.h"
string sc = ">-=#+*%@$)";
string g = "|\\-/";

void test() {
    char bar[101] = "";
    memset(bar, 0, sizeof(bar));
    int start = 0;
    while (start <= 100) {
        printf("[%-100s]|[%d%%]|[%c]\r", bar, start, g[start % 4]);
        fflush(stdout);
        bar[start++] = sc[N];
        usleep(50000);
    }
}

main.cpp

#include "test.h"
int main() {
    test();
    return 0;
}

test.h

#pragma once
#include <iostream>
#include <unistd.h>
#include <cstring>
using namespace std;
extern void test();

makefile 构建多个可执行文件

makefile 从上往下默认生成一个可执行文件

生成多个可执行问价的方法

.PHONY:all
all:mybin myexec

mybin:mybin.c
	gcc -o $@ @^
myexec:myexec.c
	gcc -o $@ @^
.PHONY:clean
	rm -rf myexec mybin

GDB-Linux 调试工具

GDB 是一个强大的调试工具,用于调试 C/C++ 程序。在编译时加入 -g 选项可以生成调试信息。

g++ test.cpp -o test -g

常用 GDB 命令

  • llist:列出当前代码。

  • rrun:运行程序。

  • nnext:单步执行,不进入函数内部。

  • sstep:单步执行,进入函数内部。

  • bbreak:在指定行或函数设置断点。

  • pprint:打印表达式的值。

  • ccontinue:继续运行程序。

  • quit:退出 GDB。

以下是一些调试命令的示例:

gdb test         # 启动 GDB
b main           # 在 main 函数设置断点
r                # 运行程序
n                # 单步执行
p var            # 打印变量 var 的值
c                # 继续运行
quit             # 退出 GDB

安装 GDB

如果系统没有安装 GDB,可以通过包管理器安装,例如:

sudo pacman -S gdb