二十二.内联函数

1.基本概念:

通过 inline 关键字声明,编译时将函数调用替换为函数体,减少函数调用开销,提高执行效率,是一种特殊的函数形式,同时保持函数封装带来的代码可读性和可维护性。

2.C++ 内联函数的声明与使用特点:

优点:减少函数调用开销提高执行效率保持代码可读性

缺点:增加代码体积不适合复杂函数不适合频繁修改的函数

C++ 通过 inline 关键字声明内联函数,编译器会尝试将函数体直接嵌入到调用位置,而非通过传统的函数调用机制(压栈、跳转、恢复现场)。

基本语法

// 声明并定义内联函数(通常在头文件中)
inline int max(int a, int b) {
    return (a > b) ? a : b;
}

int main() {
    int x = 10, y = 20;
    int result = max(x, y);  // 编译时可能被展开为:int result = (x > y) ? x : y;
    return 0;
}

关键特性:inline 是给编译器的 “建议” 而非 “命令”:编译器会根据函数复杂度(如是否包含循环、递归、多分支等)决定是否真正内联。对于复杂函数,编译器可能忽略 inline 声明,按普通函数处理。内联函数通常需要在头文件中定义:因为编译器在编译每个调用该函数的源文件(.cpp)时,都需要看到函数体才能完成内联展开。若仅在 .cpp 中定义,其他文件调用时可能导致链接错误。

3.在 C++ 类中,在类声明内部定义的成员函数会被编译器自动视为内联函数,无需显式添加 inline 关键字。

class Circle {
private:
    double radius;
public:
    // 类内定义的成员函数,默认视为内联
    double getRadius() const { 
        return radius; 
    }
    
    void setRadius(double r) { 
        radius = r; 
    }
    
    // 类外定义的成员函数,若要内联需显式加inline
    inline double area() const;  // 声明
};

// 类外定义内联函数(需与声明处的inline匹配)
inline double Circle::area() const {
    return 3.14159 * radius * radius;
}

4.内联函数 vs 宏(C 语言)

5.内联函数注意事项

1. 内联是建议性的编译器可能忽略 inline 关键字复杂函数可能不会被内联
2. 不适合内联的情况包含循环或递归的函数函数体过大的函数包含虚函数调用的函数
3. 调试困难内联函数在调试时可能无法单步执行可能导致错误定位困难

二十三.程序分为几个段

C 语言程序在内存中通常分为以下5个段:代码段(Text Segment),数据段(Data Segment),BSS段(Block Started by Symbol),堆,栈。

1. 代码段(Text Segment)—— 工厂的 “操作手册”

作用存放程序的执行指令(机器码),比如函数体、循环逻辑、条件判断等。只读(防止意外修改指令)、共享(多个程序实例可共用同一份代码)。

就像工厂里的 “操作手册”,所有工人(CPU)都按照手册上的步骤干活,手册不能涂改。

// 这段函数的机器码会存放在代码段
inline int add(int a, int b) {  // 内联函数展开后,其指令会直接嵌入调用处的代码段
    return a + b;
}

int main() {  // main函数的指令也在代码段
    int x = 3;
    int y = add(x, 5);  // 内联后,这里直接变成 x + 5 的指令,和main的代码“融合”在代码段
    return 0;
}

2. 数据段(Data Segment)—— 工厂的 “成品仓库”

作用:存放已初始化全局变量和(全局和局部)静态变量(包括类的静态成员)。特点:可读可写,程序启动时就分配内存,退出时释放。

就像工厂里存放 “已打包好的成品” 的仓库,这些成品从一开始就存在,随时可以取用或修改。

int global_var = 10;  // 已初始化的全局变量 → 存放在数据段
static int static_var = 20;  // 已初始化的静态变量 → 存放在数据段

int main() {
    static int local_static = 30;  // 已初始化的局部静态变量 → 也在数据段(生命周期和程序一致)
    return 0;
}

3. BSS 段(Block Started by Symbol)—— 工厂的 “空仓库”

作用:存放未初始化全局变量和(全局和局部)静态变量编译器会自动初始化为 0)。

特点:可读可写,程序启动时分配内存(但不占用可执行文件的空间,仅记录大小)。

就像工厂里的 “空仓库”,里面暂时没放东西,但有明确的空间,需要时可以直接用(默认装满 “0”)。

int uninit_global;  // 未初始化的全局变量 → 存放在BSS段(自动初始化为0)
static int uninit_static;  // 未初始化的静态变量 → 存放在BSS段(自动初始化为0)

int main() {
    static int uninit_local_static;  // 未初始化的局部静态变量 → 也在BSS段
    return 0;
}

4. 栈(Stack)—— 工厂的 “临时工作台”

作用:存放函数调用时的上下文(如返回地址、参数)、局部变量、临时变量

特点:

自动管理:函数进入时分配,函数退出时自动释放(“先进后出”,像叠盘子)。

大小固定(编译时确定),超出会导致 “栈溢出”(比如递归太深)。从高地址向低地址增

形象比喻:就像工人的 “临时工作台”,每次干活(调用函数)时临时占用,干完就清空,下一个人继续用。

void func(int a) {  // 参数a存放在栈上
    int b = 5;  // 局部变量b存放在栈上
    // 函数结束时,b和a自动从栈上释放
}

int main() {
    int x = 3;  // 局部变量x存放在栈上
    func(x);    // 调用func时,栈上会临时存放返回地址、参数a的值
    return 0;   // main结束时,x从栈上释放
}

5. 堆(Heap)—— 工厂的 “临时货柜区”

作用存放动态分配的内存(比如用new/malloc申请的空间)

特点:手动管理:需要用delete/free释放,否则会导致 “内存泄漏”。大小不固定:程序运行时可动态扩展(受限于系统内存)。从低地址向高地址增长

就像工厂外的 “临时货柜区”,需要时租一个货柜(申请内存),不用了要手动归还(释放),否则会一直占用空间。

int main() {
    int* p = new int[10];  // 动态分配的数组 → 存放在堆上
    // ... 使用p ...
    delete[] p;  // 必须手动释放,否则堆内存泄漏
    return 0;
}

函数调用上下文(Function Call Context)可以理解为:函数被调用时,CPU 为了保证 “调用完能准确回到原来的位置继续执行” 而临时保存的一系列关键信息

包括:函数返回地址(调用者中下一步要执行的指令位置);函数参数和局部变量(存放在栈上);调用者正在使用的寄存器值(临时备份)。

#include <iostream>

//举例说明
void funcB(int x) {  // 被调用的函数
    int y = x + 10;  // 局部变量
    std::cout << y << std::endl;
}

void funcA() {  // 调用者函数
    int a = 5;
    funcB(a);    // 调用funcB,这里会触发上下文保存
    a = a + 1;   // funcB执行完后,要回到这里继续执行
}

int main() {
    funcA();
    return 0;
}

与前一个知识点的关联:

普通函数调用的上下文保存和恢复是有性能开销的(压栈、弹栈、跳转这些操作会消耗 CPU 时间)。而内联函数因为被直接嵌入到调用处,不需要跳转,也就不需要保存和恢复上下文,这就是它能减少开销的核心原因。

二十四..c 文件转换为可执行程序的过程

阶段 1:预处理(Preprocessing):处理源代码中的预处理指令(以 # 开头的命令),生成纯 C 代码(无预处理指令)

展开 #include 头文件(比如把 stdio.h 的内容插入到当前文件);替换 #define 宏(比如把代码中的 NUM 替换为 10);删除注释(// 或 /.../);处理条件编译(如 #ifdef/#ifndef,保留符合条件的代码)。

gcc -E main.c -o main.i

阶段 2:编译(Compilation)将预处理后的纯 C 代码(.i 文件)转换为汇编语言代码(人类可读的机器指令助记符)。

语法分析(检查代码是否符合 C 语法规则,如括号是否匹配、关键字是否正确);语义分析(检查代码逻辑是否合理,如变量是否未定义就使用);代码优化(简化运算、消除冗余代码,提升执行效率);生成汇编指令(将 C 代码翻译为对应 CPU 架构的汇编语言,如 x86、ARM 的汇编)。

gcc -S main.i -o main.s

main.s中是汇编指令,例如 printf 调用会被转换为 call printf 等汇编语句(不同 CPU 架构的汇编语法可能不同)。

阶段 3:汇编(Assembly)将汇编语言代码(.s 文件)转换为机器码(二进制指令,CPU 可直接执行)。

把汇编指令一一对应为 CPU 能识别的二进制编码(如 call 指令对应 0xe8 开头的二进制数);生成目标文件(包含二进制机器码、符号表等信息)。

输出:目标文件(以 .o 为后缀,Linux 下)或 .obj(Windows 下,如 hello.o)。

gcc -c main.s -o main.o是二进制文件(用文本编辑器打开会显示乱码),包含 main 函数的机器码,但此时还不能直接运行(因为 printf 等函数的实现还未关联)

阶段 4:链接(Linking)将多个目标文件(.o)和所需的库文件(如 C 标准库)合并,解决符号引用,生成最终可执行程序。

符号解析:找到代码中引用的外部符号(如 printf 函数)的实际地址(这些符号的实现通常在标准库中);重定位:调整目标文件中符号的地址(因为每个目标文件默认从地址 0 开始,合并后需要重新计算实际地址);合并段:将多个目标文件的代码段、数据段等合并为一个整体。

gcc  main.o -o main

# 执行链接,生成可执行程序main

main 是最终可执行文件,包含所有必要的机器码(包括 main 和 printf 的实现),可以直接运行:./main

总结:完整流程

hello.c →[预处理]→ hello.i →[编译]→ hello.s →[汇编]→ hello.o →[链接]→ hello(可执行程序)、

二十五.交叉编译

基本概念 :交叉编译是在一个平台上编译生成能在另一个不同架构平台上运行的程序的过程。
应用场景:嵌入式系统开发(如 ARM、MIPS 等),资源受限设备的程序开发,多平台软件开发。
交叉编译工具链:命名通常为"目标架构-工具名",如 arm-linux-gnueabihf-gcc
 ARM 交叉编译示例 :arm-linux-gnueabihf-gcc main.c -o main.elf

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐