嵌入式开发学习日志(c语言预处理及指针入门) day12 持续更新中
📅 2026/8/4 6:54:15
👁️ 阅读次数
📝 编程学习
一、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 = # // 2. 定义同时赋值 p = # // 3. 赋值:让指针指向 num⚠️ 野指针:未初始化、或者指向已经释放内存的指针。
- 规避方案:指针初始化为
NULL,或赋值合法变量地址。
4. 两个核心运算符
| 运算符 | 名称 | 作用 | 示例 |
|---|---|---|---|
& | 取地址 | 获取变量首字节地址 | p = #(修改指针指向) |
* | 解引用 | 访问指针指向内存中的数据 | *p = 666;(修改指向空间的值) |
极易混淆区分:
p = #→ 修改指针本身,让指针指向 num。*p = num;→ 修改指针指向空间的数据,指针指向不变。
5. 指针算术运算
允许运算:+、-、++、--。*、/、%对指针无意义。
- 指针 ± 整数:向前/向后偏移若干个元素,不是字节!
char *pc; pc+1;// 向后偏移 1 字节int *pi; pi+1;// 向后偏移 4 字节 (假设 int 为 4 字节)
- 指针 - 指针:
- 前提:同类型、指向同一块连续内存(数组)。
- 结果:两个地址之间相隔的元素个数。
- 公式:
(地址差值) / sizeof(基类型)
- 比较大小:指针可以使用
< > == !=比较。
6. 指针作函数参数
- 修改变量值:想要在函数内修改主函数变量的值,必须传递变量地址(指针传递)。
- 仅读取:可以传值,也可以传地址。
- 多返回值:使用指针参数带回结果。
四、指针与一维数组
- 数组名
a等价于&a[0],代表数组首元素地址。 - 数组名是地址常量,不能赋值(如
a = xxx;非法)。 - 下标与指针等价:
a[0]<==>*aa[1]<==>*(a+1)a[n]<==>*(a+n)
- 函数形参:
数组传参本质是传递数组首元素地址(退化为指针)。void print_array(int a[], int len); // 写法1 void print_array(int *pa, int len); // 写法2 (本质相同)
五、易错点汇总
- 宏:末尾不要加分号,带参宏所有参数必须包裹括号。
- 头文件:
#ifndef用于防止重复包含。 - 赋值:
p=&num(改指向)和*p=num(改值)含义完全不同。 - 指针减法:两个指针相减得到的是元素个数,不是字节差值。
- 偏移量:指针运算偏移单位由基类型决定。
- 数组名:是常量,不能被赋值。
- 崩溃原因:使用未初始化指针(野指针)会导致程序崩溃。
编程学习
技术分享
实战经验