高地址
┌─────────────────┐
│ 内核空间 │ (3GB-4GB, 用户程序不可访问)
├─────────────────┤
│ 栈 (Stack) │ ↓ 向低地址增长
│ │ 局部变量、函数参数、返回地址
├─────────────────┤
│ ↓ │
│ (未使用区域) │
│ ↑ │
├─────────────────┤
│ 堆 (Heap) │ ↑ 向高地址增长
│ │ malloc/calloc/realloc分配
├─────────────────┤
│ BSS段(未初始化) │ 未初始化的全局/静态变量
│ │ 自动初始化为0
├─────────────────┤
│ 数据段(已初始化)│ 已初始化的全局/静态变量
│ (Data) │ 常量数据(只读数据段)
├─────────────────┤
│ 代码段(Text) │ 程序机器码指令
│ │ 只读,可共享
└─────────────────┘
低地址
| 区域 | 存储内容 | 生命周期 | 大小 | 特点 |
|---|---|---|---|---|
| 栈 | 局部变量、函数参数 | 函数作用域 | 固定(通常8MB) | 自动管理,速度快 |
| 堆 | 动态分配的内存 | 手动管理 | 可增长 | 需手动释放 |
| 数据段 | 初始化的全局/静态变量 | 整个程序 | 固定 | 可读写 |
| BSS段 | 未初始化的全局/静态 | 整个程序 | 固定 | 自动清零 |
| 代码段 | 程序指令 | 整个程序 | 固定 | 只读 |
void func() {
int a = 10; // 栈上
char buf[100]; // 栈上
int *p = &a; // p在栈上,指向栈上的a
// 生命周期: 函数开始到结束
// 函数返回后,所有局部变量销毁
}
特点:
陷阱示例:
int* dangerous() {
int x = 42;
return &x; // ? 返回局部变量地址,函数返回后x已销毁
}
int main() {
int *p = dangerous();
printf("%d\n", *p); // 未定义行为!可能崩溃或打印垃圾值
}
void counter() {
static int count = 0; // 只初始化一次
count++;
printf("Count: %d\n", count);
}
int main() {
counter(); // 输出: Count: 1
counter(); // 输出: Count: 2
counter(); // 输出: Count: 3
}
特点:
内存位置:
void func() {
static int initialized = 10; // 数据段
static int uninitialized; // BSS段(自动初始化为0)
}
int global_var = 100; // 数据段
int uninitialized_global; // BSS段
void func() {
printf("%d\n", global_var); // 任何函数都能访问
}
int main() {
printf("%d\n", uninitialized_global); // 输出: 0 (自动初始化)
}
特点:
extern关键字:
// file1.c
int shared_var = 42;
// file2.c
extern int shared_var; // 声明在其他文件定义的变量
printf("%d\n", shared_var); // 可以访问
static全局变量(文件作用域):
// file1.c
static int private_var = 10; // 只在file1.c内可见
// file2.c
extern int private_var; // ? 链接错误!无法访问
void func() {
register int i; // 建议编译器将i放在寄存器中
for (i = 0; i < 1000; i++) {
// 快速访问
}
// ? 不能取地址
// int *p = &i; // 编译错误
}
特点:
// 字面常量
int a = 100; // 100在代码段或立即数
char *str = "Hello"; // "Hello"在只读数据段
// const修饰的变量
const int MAX = 100; // 可能在栈、数据段或优化掉
void func() {
const int local = 10; // 栈上,但不可修改
// local = 20; // ? 编译错误
}
// const指针
const int *p1; // 指向常量的指针(不能通过p1修改值)
int const *p2; // 同上
int *const p3 = &a; // 常量指针(p3不能指向其他地址)
const int *const p4; // 都不能改
字符串字面量陷阱:
register
char *str1 = "Hello"; // 指向只读数据部分
str1[0] = 'h'; // ? 段错误!尝试修改只读内存
char str2[] = "Hello"; // 数组位于栈中,复制字符串
str2[0] = 'h'; // ? 可以更改
三、作用域解析
3.1 块级作用域
int main() {
int x = 1;
{
int x = 2; // 内层作用域的x,遮蔽外层x
printf("%d\n", x); // 输出: 2
}
printf("%d\n", x); // 输出: 1
for (int i = 0; i < 10; i++) {
// i仅在for循环内部可见
}
// printf("%d", i); // ? 编译错误(C99标准)
}
3.2 文件级作用域
// file1.c
int global = 10; // 全局可访问
static int file_only = 20; // 仅限本文件可见
// file2.c
extern int global; // ? 可以引用
extern int file_only; // ? 链接错误
3.3 函数级作用域
// 仅有goto标签拥有函数级作用域
void func() {
goto end; // 可以跳转到函数内的任意标签
if (1) {
end:
printf("End\n");
}
}
3.4 原型级作用域
// 参数名称仅在函数原型中有效
void func(int x, int y); // x和y仅在此行有效
void func(int a, int b) { // 可以使用不同名称
// ...
}
四、常见陷阱与错误点
4.1 ?? 返回局部变量地址
// ? 风险示例1: 返回局部变量指针
int* get_number() {
int x = 42;
return &x; // x在函数返回后被销毁
}
// ? 风险示例2: 返回局部数组
char* get_string() {
char str[100] = "Hello";
return str; // str位于栈中,函数返回后无效
}
// ? 正确方法1: 使用静态变量
char* get_string_safe() {
static char str[100] = "Hello"; // 存储在数据段,不会被销毁
return str;
}
// ? 正确方法2: 动态分配
char* get_string_dynamic() {
char *str = malloc(100);
strcpy(str, "Hello");
return str; // 调用者负责释放
}
// ? 正确方法3: 调用者提供缓冲区
void get_string_buffer(char *buf, size_t len) {
strncpy(buf, "Hello", len - 1);
buf[len - 1] = '\0';
}
4.2 ?? 未初始化的局部变量
void dangerous() {
int x; // 未初始化,包含随机值
if (x > 10) { // ? 未定义的行为
printf("Greater\n");
}
}
// ? 正确方法
void safe() {
int x = 0; // 显式初始化
if (x > 10) {
printf("Greater\n");
}
}
静态/全局变量自动初始化为零:
int global; // 自动设为0
static int local; // 自动设为0
void func() {
int x; // 随机值!
static int y; // 自动设为0
}
4.3 ?? 修改字符串常量
// ? 错误
char *str = "Hello";
str[0] = 'h'; // 段错误!修改只读内存
// ? 正确
char str[] = "Hello"; // 字符数组,位于栈中,可修改
str[0] = 'h';
字符串常量存储在只读数据段:
char *p1 = "Hello";
char *p2 = "Hello";
// p1和p2可能指向相同的地址(编译器优化)
printf("%p %p\n", p1, p2); // 可能相同
4.4 ?? 数组越界
int arr[10];
arr[10] = 100; // ? 越界!未定义的行为
// 可能的后果:
// 1. 覆盖栈上的其他变量
// 2. 覆盖返回地址(缓冲区溢出攻击)
// 3. 段错误
// 4. 表面上正常运行(最危险!)
栈溢出实例:
void overflow() {
int arr[5];
int important = 42;
arr[5] = 100; // 可能覆盖关键数据
printf("%d\n", key_data); // 可能显示100而非42
}
4.5 ?? 悬空指针 (Dangling Pointer)
int *p = malloc(sizeof(int));
*p = 42;
free(p);
// p现为悬空指针
*p = 100; // ? 访问已释放的内存,行为未定义
printf("%d\n", *p); // ? 可能崩溃或显示垃圾值
// ? 正确操作
free(p);
p = NULL; // 避免误用
if (p != NULL) {
*p = 100; // 不会执行
}
函数返回后的悬空指针:
int *p;
{
int x = 42;
p = &x;
} // x销毁
// p现为悬空
printf("%d\n", *p); // ? 行为未定义
4.6 ?? 内存泄漏
// ? 泄漏示例1: 指针丢失
void leak1() {
int *p = malloc(100);
// 忘记释放p
} // 内存永远无法回收
// ? 泄漏示例2: 指针覆盖
void leak2() {
int *p = malloc(100);
p = malloc(200); // 前面100字节泄漏!
free(p); // 仅释放200字节
}
// ? 正确操作
void no_leak() {
int *p = malloc(100);
// 使用p...
free(p);
p = NULL;
}
4.7 ?? 栈溢出 (Stack Overflow)
// ? 大数组导致栈溢出
void overflow() {
int large[10000000]; // 40MB,超出默认栈大小(8MB)
// 段错误!
}
// ? 无限递归
void infinite() {
infinite(); // 栈溢出
}
// ? 正确操作: 使用堆
void no_overflow() {
int *large = malloc(10000000 * sizeof(int));
// 使用large...
free(large);
}
查看和修改栈大小:
# 查看栈大小限制
ulimit -s
# 设置为16MB
ulimit -s 16384
4.8 ?? 静态变量的陷阱
// ? 多线程问题
char* get_string() {
static char buf[100]; // 所有线程共享!
sprintf(buf, "线程 %ld", pthread_self());
return buf; // 竞争条件
}
// ? 使用线程局部存储
__thread char buf[100];
char* get_string_safe() {
sprintf(buf, "线程 %ld", pthread_self());
return buf;
}
静态变量仅初始化一次:
void func(int x) {
static int count = x; // ? 仅在首次调用时设置
printf("%d\n", count);
}
int main() {
func(1); // 显示: 1
func(2); // 显示: 1 (非2!)
func(3); // 显示: 1
}
4.9 ?? 数组名退化为指针
void print_size(int arr[]) {
// arr实际上是指针,非数组!
printf("%zu\n", sizeof(arr)); // 显示: 8 (指针大小)
}
int main() {
int arr[10];
printf("%zu\n", sizeof(arr)); // 显示: 40 (数组大小)
print_size(arr);
}
// ? 正确操作: 传递大小
void print_size_correct(int arr[], size_t size) {
printf("%zu\n", size);
}
int main() {
int arr[10];
print_size_correct(arr, sizeof(arr) / sizeof(arr[0]));
}
4.10 ?? 结构体内存对齐
struct Example {
char a; // 1字节
int b; // 4字节
char c; // 1字节
};
printf("%zu\n", sizeof(struct Example)); // 显示: 12 (非6!)
内存布局:
地址 内容
0x00 a (1字节)
0x01 [填充] (3字节)
0x04 b (4字节)
0x08 c (1字节)
0x09 [填充] (3字节)
总计: 12字节
优化: 重新排序成员
struct Optimized {
int b; // 4字节
char a; // 1字节
char c; // 1字节
// 自动填充2字节
};
printf("%zu\n", sizeof(struct Optimized)); // 显示: 8
4.11 指针与数组的误解
int arr[10];
int *p = arr;
// 等价
arr[3] == p[3] == *(arr + 3) == *(p + 3)
// 不等
sizeof(arr) // 40 (整个数组)
sizeof(p) // 8 (指针尺寸)
// arr不可再赋值
// arr = p; // 编译出错
// p可以
p = arr; // 正确
五、内存检测工具
5.1 Valgrind (内存错误探测)
gcc -g program.c -o program
valgrind --leak-check=full ./program
检测问题:
内存泄露
访问未初始化内存
越界访问
释放后使用
5.2 AddressSanitizer
gcc -fsanitize=address -g program.c -o program
./program
优势:
比Valgrind快
检测栈/堆/全局变量越界
检测use-after-free
5.3 静态分析
# Clang静态分析器
clang --analyze program.c
# Cppcheck
cppcheck program.c
六、最佳实践
6.1 初始化变量
// 好习惯
int x = 0;
char *p = NULL;
int arr[10] = {0}; // 全部设为0
// 危险
int x; // 垃圾值
char *p; // 无效指针
6.2 检查malloc返回值
int *p = malloc(sizeof(int));
if (p == NULL) {
fprintf(stderr, "内存分配失败\n");
return -1;
}
// 使用p...
free(p);
p = NULL;
6.3 避免魔法数字
// 不佳
char buf[256];
// 较好
#define BUFFER_SIZE 256
char buf[BUFFER_SIZE];
6.4 使用const保护数据
void process(const int *data, size_t size) {
// data[0] = 10; // 编译错误,防止数据被修改
printf("%d\n", data[0]); // 可以读取
}
6.5 限制全局变量
// 避免
int global_counter; // 任何地方都可修改
// 更好: 封装
static int counter = 0;
int get_counter() {
return counter;
}
void increment_counter() {
counter++;
}
七、内存布局实例
7.1 完整示例
#include <stdio.h>
#include <stdlib.h>
int global_init = 100; // 数据段
int global_uninit; // BSS段
const int CONSTANT = 999; // 只读数据段
void print_addresses() {
static int static_var = 42; // 数据段
int local = 10; // 栈
int *heap = malloc(sizeof(int)); // 堆
printf("代码段 - 函数地址: %p\n", (void*)print_addresses);
printf("只读数据段 - 常量: %p\n", (void*)&CONSTANT);
printf("数据段 - 全局已初始化: %p\n", (void*)&global_init);
printf("数据段 - 静态变量: %p\n", (void*)&static_var);
printf("BSS段 - 全局未初始化: %p\n", (void*)&global_uninit);
printf("堆 - malloc分配: %p\n", (void*)heap);
printf("栈 - 局部变量: %p\n", (void*)&local);
free(heap);
}
int main() {
print_addresses();
return 0;
}
典型输出:
代码段 - 函数地址: 0x400566
只读数据段 - 常量: 0x400700
数据段 - 全局已初始化: 0x601040
数据段 - 静态变量: 0x601044
BSS段 - 全局未初始化: 0x601050
堆 - malloc分配: 0x1234000
栈 - 局部变量: 0x7fffffff
八、调试技巧
8.1 打印变量地址和值
int x = 42;
printf("地址: %p, 值: %d\n", (void*)&x, x);
// GDB
(gdb) x/10x &variable // 展示10个十六进制数值
(gdb) x/s pointer // 展示字符串
(gdb) print sizeof(var)
#include <sys/resource.h>
void check_stack() {
struct rusage usage;
getrusage(RUSAGE_SELF, &usage);
printf("最高栈使用量: %ld KB\n", usage.ru_maxrss);
}
变量声明:
内存管理:
数组和指针:
作用域和生命周期:
编译和测试:
-Wall -Wextra
编译选项牢记:
C语言不会保护你不受自身错误的影响,你必须自行谨慎!
扫码加好友,拉您进群



收藏
