C程序的内存布局分为以下几个段:
1. 代码段(Text Segment):程序启动时分配,位置固定
2. 数据段(Data Segment:程序启动时分配,位置固定
a. 初始化数据段(Initialized Data Segment)
b. 未初始化数据段(Uninitialized Data Segment,也称为BSS段)
3. 栈(Stack):函数调用时自动分配,调用结束自动释放
4. 堆(Heap):程序员控制分配和释放时机
常见用法实例说明:
#include <stdio.h> #include <stdlib.h> #include <string.h> /* 全局变量和静态变量的内存分配时机说明: 1. 分配时间:程序加载到内存时(程序启动前) 2. 分配位置:数据段(初始化部分)和BSS段(未初始化部分) 3. 释放时间:程序结束时 */ int global_init = 10; // 第11行:程序加载时分配在数据段,已初始化 int global_uninit; // 第12行:程序加载时分配在BSS段,未初始化,默认为0 static int static_global = 20; // 第13行:程序加载时分配在数据段,已初始化 const int const_global = 30; // 第14行:程序加载时分配在只读数据段 const char *global_str = "Hello"; // 第15行:指针在数据段,字符串字面量"Hello"在只读段 // 函数声明不分配内存,只是告诉编译器函数的存在 void test_function(); /* 第20行:函数定义开始 函数代码本身在程序加载时分配在代码段 函数内部的具体分配时机在调用时确定 */ void test_function() { /* 栈上变量的分配时机: 分配时间:函数被调用时 释放时间:函数返回时 */ int local_var = 100; // 第29行:函数调用时在栈上分配内存并初始化为100 /* 静态局部变量的分配时机: 分配时间:程序加载时(与全局变量相同) 初始化时间:第一次执行到该声明时 释放时间:程序结束时 */ static int static_local = 0; // 第36行:程序加载时分配在数据段,第一次调用时初始化为0 printf("local_var = %d, static_local = %d\n", local_var, static_local); static_local++; // 第39行:修改已存在的静态变量,不涉及内存分配 } /* 动态内存分配函数示例 分配时机:调用malloc/calloc/realloc时 释放时机:调用free时或程序结束时(泄漏) */ int* create_int_array(int size) { /* 第47行:函数调用时在栈上分配指针变量arr 但arr指向的内存尚未分配 */ int *arr = NULL; /* 第52行:调用malloc时在堆上分配内存 分配大小:size * sizeof(int) 字节 如果没有足够内存,返回NULL */ arr = (int*)malloc(size * sizeof(int)); if (arr != NULL) { /* 第59行:循环中的i是栈变量 每次循环迭代不重新分配,只是修改值 */ for (int i = 0; i < size; i++) { /* 第63行:不分配内存,只是给已分配的内存赋值 arr[i] 是 *(arr + i) 的语法糖 */ arr[i] = i * 10; } } /* 第70行:返回时,arr指针本身会被复制到返回值位置 但arr指向的堆内存保持不变 注意:arr是局部指针变量,函数返回时在栈上释放 但arr指向的堆内存不会被自动释放 */ return arr; } /* 字符串内存分配示例 展示不同字符串的内存分配方式 */ void string_memory_demo() { printf("\n=== 字符串内存分配示例 ===\n"); /* 第84行:字符串字面量 分配时机:程序加载时 分配位置:只读数据段 生命周期:整个程序运行期间 */ char *str_literal = "Hello World"; // 指针在栈,字符串在只读段 /* 第91行:字符数组 分配时机:函数调用时 分配位置:栈 大小:13字节(12个字符 + '\0') 生命周期:函数执行期间 */ char stack_str[] = "Stack String"; // 整个数组在栈上 /* 第99行:动态分配的字符串 分配时机:调用malloc时 分配位置:堆 生命周期:直到调用free */ char *heap_str = (char*)malloc(20 * sizeof(char)); if (heap_str != NULL) { /* 第107行:不分配内存,只是复制数据到已分配的内存 strcpy会复制字符串包括结束符'\0' */ strcpy(heap_str, "Heap String"); } printf("str_literal: %p (只读段), 值: %s\n", str_literal, str_literal); printf("stack_str: %p (栈), 值: %s\n", stack_str, stack_str); printf("heap_str: %p (堆), 值: %s\n", heap_str, heap_str); /* 第117行:必须手动释放堆内存 不释放会导致内存泄漏 */ free(heap_str); heap_str = NULL; // 第121行:避免野指针,不分配新内存 } /* 结构体内存分配示例 */ struct Person { char name[50]; // 第126行:结构体定义时不分配内存,只是描述布局 int age; // 第127行:只是声明成员类型 double salary; // 第128行:只是声明成员类型 }; void struct_memory_demo() { printf("\n=== 结构体内存分配示例 ===\n"); /* 第134行:栈上分配结构体 分配时机:函数调用时 分配大小:sizeof(struct Person) 字节 生命周期:函数执行期间 */ struct Person person1; // 栈上分配,未初始化 /* 第141行:不分配内存,只是给已分配的结构体成员赋值 */ strcpy(person1.name, "Alice"); person1.age = 30; // 第143行:赋值,不分配内存 person1.salary = 5000.0; // 第144行:赋值,不分配内存 /* 第146行:堆上分配结构体 分配时机:调用malloc时 */ struct Person *person2 = (struct Person*)malloc(sizeof(struct Person)); if (person2 != NULL) { /* 第152行:通过指针访问结构体成员 注意:person2->name 等价于 (*person2).name */ strcpy(person2->name, "Bob"); person2->age = 35; person2->salary = 6000.0; } printf("person1: %s, %d, %.2f (栈上)\n", person1.name, person1.age, person1.salary); printf("person2: %s, %d, %.2f (堆上,地址: %p)\n", person2->name, person2->age, person2->salary, person2); free(person2); // 第165行:释放堆上结构体 } /* 数组内存分配示例 */ void array_memory_demo() { printf("\n=== 数组内存分配示例 ===\n"); /* 第172行:栈上数组 分配时机:函数调用时 分配大小:5 * sizeof(int) = 20字节(通常) 生命周期:函数执行期间 */ int stack_array[5] = {1, 2, 3, 4, 5}; // 声明+初始化 /* 第179行:堆上数组 分配时机:调用malloc时 */ int *heap_array = (int*)malloc(5 * sizeof(int)); if (heap_array != NULL) { /* 第185-189行:给数组元素赋值,不分配新内存 */ for (int i = 0; i < 5; i++) { heap_array[i] = (i + 1) * 10; // 赋值,不分配 } } /* 第191行:动态调整数组大小 分配时机:调用realloc时 可能的行为: 1. 原位置扩展:如果后面有足够空间 2. 新位置分配:如果后面空间不够,会分配新内存块,复制数据,释放旧内存块 */ heap_array = (int*)realloc(heap_array, 10 * sizeof(int)); if (heap_array != NULL) { /* 第200-202行:初始化新分配的部分 */ for (int i = 5; i < 10; i++) { heap_array[i] = (i + 1) * 10; // 赋值 } } printf("栈数组地址: %p\n", stack_array); printf("堆数组地址: %p\n", heap_array); free(heap_array); // 第209行:释放堆上数组 } /* 指针和内存的高级示例 */ void pointer_advanced_demo() { printf("\n=== 指针高级示例 ===\n"); /* 第216行:声明指针变量 分配时机:函数调用时在栈上分配指针变量本身(通常是4或8字节) 但指针指向的内存尚未分配 */ int *ptr1, *ptr2, *ptr3; /* 第222行:分配堆内存,ptr1指向这块内存 */ ptr1 = (int*)malloc(sizeof(int)); *ptr1 = 100; // 第224行:赋值,不分配 /* 第226行:ptr2指向ptr1指向的同一块内存 不分配新内存,只是复制指针值 */ ptr2 = ptr1; /* 第231行:分配新的堆内存,ptr3指向新内存 */ ptr3 = (int*)malloc(sizeof(int)); *ptr3 = 200; // 第233行:赋值 printf("ptr1: %p, *ptr1 = %d\n", ptr1, *ptr1); printf("ptr2: %p, *ptr2 = %d (与ptr1相同地址)\n", ptr2, *ptr2); printf("ptr3: %p, *ptr3 = %d\n", ptr3, *ptr3); /* 第239行:释放ptr1指向的内存 注意:ptr2现在成为野指针,因为它指向已释放的内存 */ free(ptr1); ptr1 = NULL; // 第243行:避免野指针 /* 第245行:ptr2现在是野指针,不应再使用 printf("*ptr2 = %d\n", *ptr2); // 危险!未定义行为 */ free(ptr3); // 第249行:释放ptr3指向的内存 ptr3 = NULL; } /* 内存泄漏示例 */ void memory_leak_demo() { printf("\n=== 内存泄漏示例 ===\n"); /* 第257行:分配内存但忘记释放 - 内存泄漏! */ int *leak = (int*)malloc(100 * sizeof(int)); /* 第260行:重新赋值指针,丢失对原内存的引用 */ leak = (int*)malloc(50 * sizeof(int)); // 之前分配的100个int泄漏了! /* 第263行:只释放了第二次分配的内存 */ free(leak); /* 正确做法应该是: int *ptr1 = malloc(100 * sizeof(int)); // 使用ptr1... free(ptr1); // 先释放 ptr1 = malloc(50 * sizeof(int)); // 再重新分配 // 使用ptr1... free(ptr1); */ } int main() { printf("=== C语言内存分配时机详解 ===\n"); /* 第279行:调用函数,创建新的栈帧 栈帧中包含函数的局部变量、参数、返回地址等 */ test_function(); /* 第284行:再次调用,static_local保持之前的值 */ test_function(); /* 第287行:调用动态内存分配函数 分配时机:函数执行到malloc时 */ int *dynamic_array = create_int_array(5); if (dynamic_array != NULL) { printf("\n动态数组: "); for (int i = 0; i < 5; i++) { printf("%d ", dynamic_array[i]); } printf("\n"); /* 第299行:释放堆内存 */ free(dynamic_array); dynamic_array = NULL; } /* 调用其他演示函数 */ string_memory_demo(); struct_memory_demo(); array_memory_demo(); pointer_advanced_demo(); memory_leak_demo(); /* 第311行:main函数返回,栈上的局部变量自动释放 全局变量和静态变量在程序结束时释放 */ return 0; }复杂调用:
C语言中不允许全局变量动态初始化(但局部静态变量可以,不过也要是常量表达式,C99之后可以用非常量表达式初始化局部静态变量,但也是在程序执行到该定义时初始化一次,但全局变量不行)
#include <stdio.h> #include <stdlib.h> int complex_global = printf("全局变量初始化\n"); // 第1步 int main() { // 第2步 printf("main函数开始\n"); return 0; } [root@192 home]# gcc test1.c -o test1 test1.c:4:22: 错误:初始值设定元素不是常量 4 | int complex_global = printf("全局变量初始化\n"); // 第1步 | ^~~~~~ [root@192 home]# 在C99中,我们可以这样写: int global = 5; // 合法,常量表达式 static int x = global; // 非法,global是变量,不是常量 static int y = sizeof(int); // 合法,sizeof是常量表达式工程使用建议
1. 全局变量:
尽量少用全局变量
必须用常量表达式初始化
考虑用static限制作用域
2. 静态变量:
静态局部变量适合保持状态(只会被初始化一次,在函数调用之间保持其值不变)
注意默认初始化为0(静态变量(包括全局和局部静态)在程序加载时会被自动初始化为零值(int为0,指针为NULL),无需手动赋初值)
在多线程环境中要小心(多线程环境中要小心是因为静态变量在内存中只有一份实例,会被所有线程共享,同时修改可能导致数据竞争,需要加锁保护)
3. 常量:
能用const就用const
提高代码可读性和安全性
字符串字面量自动具有常量性
4. 内存管理:
malloc后检查NULL
free后指针置NULL
谁分配谁释放
5. 初始化:
总是初始化变量
理解不同变量的初始化时机
避免使用未初始化的变量