嵌入式开发学习日志(c语言预处理及指针入门) day12 持续更新中

📅 2026/8/4 6:54:15 👁️ 阅读次数 📝 编程学习
嵌入式开发学习日志(c语言预处理及指针入门) day12 持续更新中

一、GCC 编译的四个完整步骤

我们在终端输入一条gcc main.c -o a.out命令时,背后其实经历了 4 个独立的阶段。理解这个过程对排查编译错误(如之前的stray '\357'编码错误)非常有帮助。

阶段指令参数输入文件输出文件主要工作
1. 预处理-E.c.i展开头文件#include、宏替换#define、处理条件编译
2. 编译-S.i.s将 C 代码翻译为汇编指令
3. 汇编-c.s.o将汇编指令转为二进制机器码
4. 链接(无).o可执行文件链接系统库函数,合并目标文件

实操顺序.c.i.s.o可执行程序


二、预处理命令

预处理在编译之前执行,本质是文本替换,不进行语法运算。

1. 宏定义#define

(1) 无参宏(符号常量)
#define PI 3.1415
(2) 带参宏(宏函数)
#define ADD(a,b) ((a)+(b)) #define MAX(a,b) ((a)>(b)?(a):(b)) #define AREA(r) ((PI)*(r)*(r))

✅ 宏书写重要规范:

  • 文本替换:宏仅仅是文本替换,运行前展开。
  • 括号保护:表达式参数全部加括号,防止运算符优先级出错。
  • 命名规范:宏标识符建议大写;宏末尾不要加分号

💡 宏 VS 普通函数:

  • :预处理阶段替换,代码副本增多,程序体积变大;无函数调用开销。
  • 函数:运行时调用,需要保护/恢复现场,存在调用开销。

2. 头文件包含#include

  • #include <stdio.h>尖括号。系统头文件,编译器去系统路径(如/usr/include)查找。
  • #include "head.h"双引号。自定义头文件,优先在当前项目目录查找。

头文件内一般存放:其他头文件、类型声明、宏定义、函数声明、全局变量声明。

3. 条件编译

根据宏是否定义、表达式真假,选择性编译部分代码。

  • #if 表达式 ... #endif:表达式为真则编译。常用于#if 0注释大块代码。
  • #ifdef 宏名:如果宏已定义,编译对应代码(常用于调试信息输出)。
  • #ifndef 宏名:如果没有定义该宏,则编译代码。最经典用途:头文件防止重复包含
#ifndef __HEAD_H__ #define __HEAD_H__ // 头文件内容 #endif

三、指针

1. 基础概念

  • 内存:程序运行可用存储空间,以字节为单位编号。
  • 地址:内存字节的编号。
  • 指针:即内存地址。
  • 指针变量:专门用来存放地址的变量。

2. 指针变量定义与大小

格式基类型 *指针名;

int *pint; char *pch;
  • 基类型:代表指针指向空间的数据类型。
  • *:标识该变量是指针。
  • 指针大小:64 位系统所有指针占用8 字节;32 位系统指针占用4 字节

3. 初始化与野指针

int num = 100; int *p = NULL; // 1. 初始化为空指针(推荐) int *p = &num; // 2. 定义同时赋值 p = &num; // 3. 赋值:让指针指向 num

⚠️ 野指针:未初始化、或者指向已经释放内存的指针。

  • 规避方案:指针初始化为NULL,或赋值合法变量地址。

4. 两个核心运算符

运算符名称作用示例
&取地址获取变量首字节地址p = &num;(修改指针指向)
*解引用访问指针指向内存中的数据*p = 666;(修改指向空间的值)

极易混淆区分

  • p = &num;→ 修改指针本身,让指针指向 num。
  • *p = num;→ 修改指针指向空间的数据,指针指向不变。

5. 指针算术运算

允许运算:+-++--*/%对指针无意义。

  • 指针 ± 整数:向前/向后偏移若干个元素,不是字节!
    • char *pc; pc+1;// 向后偏移 1 字节
    • int *pi; pi+1;// 向后偏移 4 字节 (假设 int 为 4 字节)
  • 指针 - 指针
    • 前提:同类型、指向同一块连续内存(数组)。
    • 结果:两个地址之间相隔的元素个数
    • 公式:(地址差值) / sizeof(基类型)
  • 比较大小:指针可以使用< > == !=比较。

6. 指针作函数参数

  • 修改变量值:想要在函数内修改主函数变量的值,必须传递变量地址(指针传递)。
  • 仅读取:可以传值,也可以传地址。
  • 多返回值:使用指针参数带回结果。

四、指针与一维数组

  1. 数组名a等价于&a[0],代表数组首元素地址。
  2. 数组名是地址常量,不能赋值(如a = xxx;非法)。
  3. 下标与指针等价
    • a[0]<==>*a
    • a[1]<==>*(a+1)
    • a[n]<==>*(a+n)
  4. 函数形参
    void print_array(int a[], int len); // 写法1 void print_array(int *pa, int len); // 写法2 (本质相同)
    数组传参本质是传递数组首元素地址(退化为指针)。

五、易错点汇总

  • :末尾不要加分号,带参宏所有参数必须包裹括号。
  • 头文件#ifndef用于防止重复包含。
  • 赋值p=&num(改指向)和*p=num(改值)含义完全不同。
  • 指针减法:两个指针相减得到的是元素个数,不是字节差值。
  • 偏移量:指针运算偏移单位由基类型决定。
  • 数组名:是常量,不能被赋值。
  • 崩溃原因:使用未初始化指针(野指针)会导致程序崩溃。