1、数组指针 vs 指针数组
数组指针是一个指针变量,它存储的是整个数组的地址,而不是单个元素的地址。
-
int (*p)[5]- 数组指针:指向整个数组的指针 -
int *p[5]- 指针数组:包含5个整型指针的数组 -
指针运算:
p+1会跳过整个数组的大小 -
访问元素:使用
(*p)[i]或p[0][i]访问数组元素 -
类型安全:编译器会检查数组边界
函数指针 vs 指针函数
函数指针是一个指针变量,它指向一个函数,而不是一个数据。存储的是函数在内存中的入口地址。
语法:返回值类型 (*指针变量名)(参数类型列表);
#include <stdio.h>
// 一个普通的函数
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int main() {
// 1. 声明一个函数指针 pFunc
// 这个指针可以指向任何 返回int类型、接受两个int参数 的函数
int (*pFunc)(int, int);
// 2. 让指针指向 add 函数
pFunc = &add; // & 符号可以省略,直接写 pFunc = add; 也可以
// 3. 通过指针调用函数
int result = pFunc(5, 3); // 等价于调用 add(5, 3)
printf("5 + 3 = %d\n", result); // 输出 8
// 4. 让指针指向另一个函数 subtract
pFunc = subtract;
result = pFunc(5, 3);
printf("5 - 3 = %d\n", result); // 输出 2
return 0;
}
主要应用场景:
-
回调函数:这是最重要的应用。比如在嵌入式系统中,注册一个中断处理函数,库函数并不知道你要具体执行什么操作,你只需要把一个函数指针传给它,事件发生时它就调用你提供的函数。
-
函数表/跳转表:将多个函数指针放在一个数组里,根据索引来调用不同的函数,常用于状态机或命令解析。
// 回调函数示例:模拟一个定时器中断服务 void timer_interrupt_handler(void (*user_callback)(void)) { // ... 检测定时器中断发生 ... if(interrupt_occurred) { user_callback(); // 调用用户注册的回调函数 } } // 用户自定义的函数 void my_callback() { printf("Timer expired!\n"); } int main() { // 将我的函数注册给定时器 timer_interrupt_handler(my_callback); return 0; }指针函数是一个函数,它的返回值是一个指针。名字听起来像“指针”,但本质是“函数”。
-
语法:返回值类型* 函数名(参数列表);注意
*号紧挨着返回值类型,表示返回的是一个指针。 -
#include <stdio.h> #include <stdlib.h> // 这是一个指针函数:返回一个指向int的指针 int* create_array(int size) { // 动态分配内存,返回这块内存的首地址 int* arr = (int*)malloc(size * sizeof(int)); if(arr != NULL) { for(int i = 0; i < size; i++) { arr[i] = i * 10; } } return arr; // 返回指针 } int main() { int *my_array; int len = 5; // 调用指针函数,接收它返回的指针 my_array = create_array(len); if(my_array != NULL) { for(int i = 0; i < len; i++) { printf("%d ", my_array[i]); // 输出 0 10 20 30 40 } free(my_array); // 记得释放内存 } return 0; }使用注意事项:
千万不要返回局部变量的地址! 因为函数结束后,局部变量的内存就被释放了。
// 错误示范! int* dangerous_function() { int local_var = 100; return &local_var; // 严重错误!返回后 local_var 就不存在了 }核心区别总结(非常重要!)
特性 函数指针 指针函数 本质 一个指针变量,存放函数的地址 一个函数,返回类型是指针 定义语法 int (*p)(int, int);int* func(int, int);核心用途 实现回调、动态调用函数 返回一个指针(如动态数组、结构体) 记忆口诀 “指向函数的指针” “返回指针的函数”
2、指针的大小
指针的大小是固定的,和指针的类型没有关系
32bit系统下是4个字节,64bit系统下是8字节
3、sizeof和strlen的区别
sizeof是一个运算符,strlen是一个函数,在string.h里
sizeof计算的是所占内存的大小,strlen计算的是字符串的长度
核心区别总结
| 特性 | sizeof | strlen |
|---|---|---|
| 本质 | 运算符/关键字(编译时处理) | 库函数(运行时调用) |
| 作用 | 计算数据类型或变量占用的内存大小(字节数) | 计算字符串的实际长度(直到遇到\0) |
| 处理时机 | 编译期间确定结果 | 运行期间遍历字符串计算 |
| 参数类型 | 可以是类型、变量、表达式 | 必须是字符串(以\0结尾的字符数组) |
包含\0 | ✅ 包含结束符\0 | ❌ 不包含结束符\0 |
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "hello";
char *ptr = "hello";
printf("=== 数组形式 char str[] = \"hello\" ===\n");
printf("sizeof(str) = %zu\n", sizeof(str)); // 6(整个数组大小)
printf("strlen(str) = %zu\n", strlen(str)); // 5(字符串长度)
printf("\n=== 指针形式 char *ptr = \"hello\" ===\n");
printf("sizeof(ptr) = %zu\n", sizeof(ptr)); // 4或8(指针本身大小)
printf("strlen(ptr) = %zu\n", strlen(ptr)); // 5(字符串长度)
printf("\n=== 常见陷阱 ===\n");
char arr[100] = "hello";
printf("sizeof(arr) = %zu\n", sizeof(arr)); // 100(数组总容量)
printf("strlen(arr) = %zu\n", strlen(arr)); // 5(实际内容长度)
return 0;
}
嵌入式开发中的实际应用
内存分配场景
// 正确的内存分配
char *create_string(const char *source) {
// 使用strlen计算实际需要的长度,+1是为了存放\0
size_t len = strlen(source) + 1;
char *dest = (char*)malloc(len * sizeof(char));
if(dest != NULL) {
strcpy(dest, source);
}
return dest;
}
// 错误示范:少分配了\0的空间
char *wrong_create_string(const char *source) {
// 忘记+1,可能导致内存越界
size_t len = strlen(source);
char *dest = (char*)malloc(len); // 错误!
// ...
}
缓冲区操作
-
sizeof:想成"size of memory"(内存大小)-
问:这个变量/类型占多少内存?
-
-
strlen:想成"string length"(字符串长度)-
问:这个字符串有多长?(不算结尾的\0)
-
char str[] = "hi";
// sizeof = 3 (h + i + \0)
// strlen = 2 (h + i)
掌握这个区别对于嵌入式开发中避免内存溢出、正确处理字符串非常重要!
4、C语言内存分配的方式
静态存储区分配、栈分配、堆分配
1. 静态存储区分配
在程序编译时确定大小,整个程序运行期间都存在。
#include <stdio.h>
int global_var = 100; // 全局变量 - 静态存储区
static int static_global = 200; // 静态全局变量 - 静态存储区
void func() {
static int static_local = 300; // 静态局部变量 - 静态存储区
static_local++;
printf("static_local = %d\n", static_local);
}
int main() {
printf("global_var = %d\n", global_var); // 100
printf("static_global = %d\n", static_global); // 200
func(); // 输出:static_local = 301
func(); // 输出:static_local = 302(保持状态)
return 0;
}
2. 栈分配
自动分配和释放,由编译器管理,函数调用时创建,返回时销毁。
#include <stdio.h>
void stack_example() {
int local_var = 10; // 栈分配
char buffer[100]; // 栈分配 - 100字节数组
double values[50]; // 栈分配 - 50个double
printf("局部变量地址: %p\n", &local_var);
printf("数组地址: %p\n", buffer);
// 注意:栈空间有限,大数组可能导致栈溢出
// char huge_buffer[100000]; // 危险!可能栈溢出
}
void recursive_func(int n) {
int local = n;
if(n > 0) {
recursive_func(n - 1); // 递归调用,每次都在栈上分配新的local
}
}
int main() {
stack_example();
return 0;
}
3. 堆分配
动态内存分配,手动管理,需要显式申请和释放。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
// 1. malloc - 分配指定大小的内存,不初始化
int *arr1 = (int*)malloc(10 * sizeof(int));
if(arr1 == NULL) {
printf("内存分配失败!\n");
return -1;
}
// 2. calloc - 分配并初始化为0
int *arr2 = (int*)calloc(10, sizeof(int));
// 3. realloc - 调整已分配内存的大小
arr1 = (int*)realloc(arr1, 20 * sizeof(int));
// 使用分配的内存
for(int i = 0; i < 10; i++) {
arr1[i] = i * 10;
printf("arr1[%d] = %d\n", i, arr1[i]);
}
// 字符串动态分配示例
char *str = (char*)malloc(100 * sizeof(char));
if(str != NULL) {
strcpy(str, "动态分配的字符串");
printf("%s\n", str);
}
// 4. free - 释放内存(非常重要!)
free(arr1);
free(arr2);
free(str);
// 避免野指针
arr1 = NULL;
arr2 = NULL;
str = NULL;
return 0;
}
三种分配方式对比
| 特性 | 静态存储区 | 栈 | 堆 |
|---|---|---|---|
| 分配时机 | 编译时 | 函数调用时 | 运行时(malloc等) |
| 释放时机 | 程序结束时 | 函数返回时 | 手动free时 |
| 管理方式 | 编译器 | 编译器自动 | 程序员手动 |
| 大小限制 | 较大 | 较小(通常几MB) | 受系统内存限制 |
| 访问速度 | 快 | 最快 | 较慢 |
| 生命周期 | 整个程序运行期 | 函数作用域内 | 手动控制 |
| 灵活性 | 固定大小 | 固定大小 | 可动态调整 |
嵌入式开发中的实际应用
安全的内存分配封装
// 带错误检查的内存分配
void* safe_malloc(size_t size) {
void *ptr = malloc(size);
if(ptr == NULL) {
// 嵌入式系统中可能需要重启或记录错误
printf("Fatal: Memory allocation failed!\n");
while(1); // 死循环或系统复位
}
return ptr;
}
// 带内存清零的分配
void* safe_calloc(size_t num, size_t size) {
void *ptr = calloc(num, size);
if(ptr == NULL) {
printf("Fatal: Memory allocation failed!\n");
while(1);
}
return ptr;
}
安全的内存分配封装
// 带错误检查的内存分配
void* safe_malloc(size_t size) {
void *ptr = malloc(size);
if(ptr == NULL) {
// 嵌入式系统中可能需要重启或记录错误
printf("Fatal: Memory allocation failed!\n");
while(1); // 死循环或系统复位
}
return ptr;
}
// 带内存清零的分配
void* safe_calloc(size_t num, size_t size) {
void *ptr = calloc(num, size);
if(ptr == NULL) {
printf("Fatal: Memory allocation failed!\n");
while(1);
}
return ptr;
}
常见内存错误及避免方法
// 1. 内存泄漏
void memory_leak() {
char *ptr = malloc(100);
// 忘记free(ptr)
}
// 2. 野指针
void wild_pointer() {
char *ptr = malloc(100);
free(ptr);
// ptr现在成为野指针
//ptr=NULL;//避免成为野指针
// strcpy(ptr, "危险操作"); // 未定义行为
}
// 3. 重复释放
void double_free() {
char *ptr = malloc(100);
free(ptr);
// free(ptr); // 错误!重复释放
}
// 4. 栈溢出
void stack_overflow() {
int huge_array[1000000]; // 可能栈溢出
}
嵌入式开发最佳实践
-
优先使用栈分配:简单、安全、快速
-
谨慎使用堆分配:记得配对free,避免内存泄漏
-
避免大对象栈分配:防止栈溢出
-
使用内存池:实时系统中避免内存碎片
-
初始化所有变量:避免未定义行为
理解这些内存分配方式对于编写稳定、高效的嵌入式程序至关重要!
5、struct结构体和union联合体的区别
struct结构体:不同的成员放在不同的地址中。结构体大小=所有成员大小之和。(考虑内存对齐,原因:提高读取速度,大部分是4字节对齐)
union联合体:成员共享一块地址。联合体大小=成员中所占内存最大的成员的大小。
核心区别总结
| 特性 | struct(结构体) | union(联合体) |
|---|---|---|
| 内存分配 | 所有成员独立分配内存空间 | 所有成员共享同一块内存空间 |
| 内存大小 | ≥ 所有成员大小之和(考虑对齐) | = 最大成员的大小 |
| 成员访问 | 所有成员可同时访问 | 同一时间只能一个成员有效 |
| 数据存储 | 各成员数据独立存储 | 各成员数据互相覆盖 |
| 主要用途 | 组织相关数据集合 | 节省内存、类型转换、硬件寄存器映射 |
1. struct(结构体)
基本概念
结构体为每个成员分配独立的内存空间,所有成员可以同时存在。
#include <stdio.h>
// 定义结构体
struct Student {
int id; // 4字节
char name[20]; // 20字节
float score; // 4字节
int age; // 4字节
};
int main() {
// 声明结构体变量
struct Student stu;
// 同时为所有成员赋值
stu.id = 1001;
strcpy(stu.name, "张三");
stu.score = 95.5;
stu.age = 20;
// 所有成员可同时访问
printf("学号: %d\n", stu.id);
printf("姓名: %s\n", stu.name);
printf("分数: %.1f\n", stu.score);
printf("年龄: %d\n", stu.age);
// 计算结构体大小(考虑内存对齐)
printf("结构体大小: %zu 字节\n", sizeof(struct Student));
// 可能是 4 + 20 + 4 + 4 = 32字节(实际可能因对齐更大)
return 0;
}
2. union(联合体)
基本概念
联合体所有成员共享同一块内存空间,同一时间只能有一个成员有效。
#include <stdio.h>
// 定义联合体
union Data {
int i; // 4字节
float f; // 4字节
char str[10]; // 10字节(最大成员,联合体的内存大小)
};
int main() {
union Data data;
// 同一时间只能使用一个成员
data.i = 100;
printf("data.i = %d\n", data.i); // 输出: 100
data.f = 3.14;
printf("data.f = %.2f\n", data.f); // 输出: 3.14
// 此时data.i的值已被覆盖,变得无意义!
printf("data.i after setting f = %d\n", data.i); // 无意义的值
strcpy(data.str, "hello");
printf("data.str = %s\n", data.str); // 输出: hello
// 此时data.i和data.f的值都被覆盖
// 联合体大小等于最大成员的大小
printf("联合体大小: %zu 字节\n", sizeof(union Data)); // 输出: 12字节(考虑对齐)
return 0;
}
3、详细对比
#include <stdio.h>
// 对比结构体和联合体
struct S {
int a; // 4字节
char b; // 1字节
double c; // 8字节
};
union U {
int a; // 4字节
char b; // 1字节
double c; // 8字节(最大成员)
};
int main() {
printf("=== 内存大小对比 ===\n");
printf("struct S 大小: %zu 字节\n", sizeof(struct S)); // 可能是24字节(对齐后)
printf("union U 大小: %zu 字节\n", sizeof(union U)); // 8字节(最大成员大小)
printf("\n=== 数据存储验证 ===\n");
// 结构体:各成员独立
struct S s;
s.a = 0x12345678;
s.b = 'A';
s.c = 3.14;
printf("结构体: a=0x%x, b=%c, c=%.2f\n", s.a, s.b, s.c); // 所有值都正确
// 联合体:成员互相覆盖
union U u;
u.a = 0x12345678;
printf("设置a后: a=0x%x\n", u.a);
u.b = 'B';
printf("设置b后: b=%c, a=0x%x (a被破坏)\n", u.b, u.a); // a的值被b覆盖部分
u.c = 2.71;
printf("设置c后: c=%.2f, a=0x%x (a完全被覆盖)\n", u.c, u.a); // a的值完全被c覆盖
return 0;
}
嵌入式开发中的实际应用
应用1:硬件寄存器映射(联合体优势)
// 用于访问32位寄存器的不同部分
typedef union {
uint32_t value; // 整个32位寄存器值
struct {
uint32_t low16 : 16; // 低16位
uint32_t high16 : 16; // 高16位
} bits;
struct {
uint32_t : 8; // 保留位
uint32_t config : 8; // 配置位域
uint32_t status : 8; // 状态位域
uint32_t control : 8; // 控制位域
} fields;
} hardware_reg_t;
// 使用示例
volatile hardware_reg_t *reg = (hardware_reg_t*)0x40021000;
// 方式1:整体读写
reg->value = 0x12345678;
// 方式2:位域操作
reg->fields.control = 0xAB;
// 方式3:部分读写
uint16_t low_part = reg->bits.low16;
应用2:协议数据解析(联合体优势)
// 网络协议中的数据包格式
typedef union {
uint8_t raw_data[64]; // 原始字节流
struct {
uint8_t header[4]; // 包头
uint32_t src_addr; // 源地址
uint32_t dest_addr; // 目的地址
uint16_t length; // 数据长度
uint8_t payload[50]; // 有效载荷
uint8_t checksum; // 校验和
} packet;
} network_packet_t;
// 使用:既可以按原始字节处理,也可以按结构体字段处理
应用3:传感器数据结构(结构体优势)
// 机器人传感器数据集合
typedef struct {
float temperature; // 温度
float humidity; // 湿度
uint16_t pressure; // 压力
int16_t accelerometer[3]; // 加速度计xyz
int16_t gyroscope[3]; // 陀螺仪xyz
uint32_t timestamp; // 时间戳
} sensor_data_t;
// 所有传感器数据需要同时存在,适合用结构体
sensor_data_t robot_sensors;
应用4:节省内存的场景(联合体优势)
// 嵌入式系统中内存紧张时使用联合体节省空间
typedef union {
struct {
uint8_t type;
uint32_t ip_address; // IP配置
uint16_t port;
} network_config;
struct {
uint8_t type;
uint32_t baud_rate; // 串口配置
uint8_t data_bits;
uint8_t stop_bits;
uint8_t parity;
} serial_config;
struct {
uint8_t type;
uint16_t can_id; // CAN总线配置
uint8_t can_mode;
} can_config;
} device_config_t;
// 同一时间只需要一种配置,节省了内存
device_config_t config;
使用结构体的情况:
-
需要同时存储所有成员数据时
-
数据之间相互独立,不存在"或"关系时
-
内存空间充足,不需要特别优化时
使用联合体的情况:
-
同一时间只需要一种类型的数据时
-
需要节省内存的嵌入式系统中
-
进行类型转换或位操作时
-
映射硬件寄存器时
-
解析协议数据时
419

被折叠的 条评论
为什么被折叠?



