十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C语言内存管理

C语言内存管理 C程序的内存布局分为以下几个段1. 代码段Text Segment程序启动时分配位置固定2. 数据段Data Segment程序启动时分配位置固定a. 初始化数据段Initialized Data Segmentb. 未初始化数据段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; } [root192 home]# gcc test1.c -o test1 test1.c:4:22: 错误初始值设定元素不是常量 4 | int complex_global printf(全局变量初始化\n); // 第1步 | ^~~~~~ [root192 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后检查NULLfree后指针置NULL谁分配谁释放5. 初始化总是初始化变量理解不同变量的初始化时机避免使用未初始化的变量
返回列表