
往期《C语言基础系列》回顾:链接: C语言基础之【C语言概述】 C语言基础之【数据类型】(上) C语言基础之【数据类型】(下) C语言基础之【运算符与表达式】 C语言基础之【程序流程结构】 C语言基础之【数组和字符串】(上) C语言基础之【数组和字符串】(下) C语言基础之【函数】 C语言基础之【指针】(上) C语言基础之【指针】(中) C语言基础之【指针】(下)
存储类型(Storage Class):用于定义变量或函数的存储位置、生命周期、作用域以及链接属性C语言提供了四种存储类型关键字:auto、register、static和extern
存储类型总结:
存储类型 | 作用域 | 生命周期 | 存储位置 | 特点 |
|---|---|---|---|---|
auto | 局部作用域 | 代码块内 | 栈区 | 默认存储类型,通常省略 |
register | 局部作用域 | 代码块内 | 寄存器或栈区 | 建议存储在寄存器中,提高访问速度 |
static | 局部或文件作用域 | 整个程序运行期间 | 全局区/静态区 | 静态局部变量保留值,静态全局变量隐藏 |
extern | 整个程序 | 整个程序运行期间 | 全局区/静态区 | 引用其他文件中定义的全局变量或函数 |
C语言变量的作用域分为:
代码块作用域函数作用域文件作用域普通局部变量:是在函数或代码块内部定义的变量,其作用域仅限于定义它的函数或代码块。
auto
局部变量也叫auto自动变量 (auto可写可不写)
普通局部变量的特点:
作用域:
生命周期:
存储位置:
栈(stack) 中。默认值:
代码示例:普通局部变量
#include <stdio.h>
void func()
{
int x = 10; // 局部变量
printf("x = %d\n", x);
x++;
}
int main()
{
func(); // 输出: x = 10
func(); // 输出: x = 10
return 0;
}func() 时,局部变量 x 都会重新创建并初始化为 10静态局部变量:是在函数内部定义的变量,使用 static 关键字修饰。
static 关键字修饰。
生命周期 和 存储位置 与普通局部变量不同。
静态局部变量的特点:
作用域:
生命周期:
存储位置:
静态存储区(static storage area),而不是栈中。默认值:
0空字符代码示例:静态局部变量
#include <stdio.h>
void func()
{
static int x = 10; // 静态局部变量
printf("x = %d\n", x);
x++;
}
int main()
{
func(); // 输出: x = 10
func(); // 输出: x = 11
func(); // 输出: x = 12
return 0;
}func() 时,静态局部变量 x 初始化为 10func() 时,x 的值会保留并递增普通局部变量与静态局部变量的对比:
特性 | 普通局部变量 | 静态局部变量 |
|---|---|---|
作用域 | 仅限于定义它的函数或代码块 | 仅限于定义它的函数或代码块 |
生命周期 | 函数或代码块执行期间 | 整个程序运行期间 |
存储位置 | 栈(stack) | 静态存储区(static storage area) |
初始化 | 未初始化时为垃圾值 | 自动初始化为 0 |
多次调用时的行为 | 每次调用重新初始化 | 保留上次调用的值 |
普通全局变量:是在所有函数之外定义的变量,通常位于文件的顶部。
普通全局变量的特点:
作用域:从定义点开始,直到文件结束。
生命周期:程序启动时创建,程序结束时销毁。
链接性:具有 外部链接性,可在其他文件中通过extern声明访问。
// file1.c
int globalVar = 10; // 全局变量
// file2.c
extern int globalVar; // 声明全局变量,定义在file1.c中
//声明一个变量globalVar,这个全局变量在别的文件中已经定义了,这里只是声明,而不是定义extern关键字:用于声明一个在其他文件中定义的全局变量。
extern声明不会分配内存,只是引用已经存在的全局变量。代码示例:普通全局变量
#include <stdio.h>
int globalVar = 10; // 全局变量
void func()
{
printf("全局变量: %d\n", globalVar);
}
int main()
{
func();
globalVar = 20; // 修改全局变量
func();
return 0;
}输出:
全局变量: 10
全局变量: 20静态全局变量 :是指在函数外部使用 static 关键字声明的变量。
静态全局变量的特点:
作用域:仅限于定义它的文件,其他文件无法访问该变量。
生命周期:程序启动时创建,程序结束时销毁。
链接性:具有 内部链接性,无法在其他文件中访问。
// file1.c
static int staticGlobalVar = 20; // static全局变量,仅在file1.c中可见
// file2.c
extern int staticGlobalVar; // 错误,无法访问file1.c中的static全局变量代码示例:静态全局变量
#include <stdio.h>
static int staticGlobalVar = 30; // 静态全局变量
void func()
{
printf("static全局变量: %d\n", staticGlobalVar);
}
int main()
{
func();
staticGlobalVar = 40; // 修改静态全局变量
func();
return 0;
}输出:
static全局变量: 30
static全局变量: 40普通全局变量和静态全局变量对比总结:
特性 | 普通全局变量 | 静态全局变量 |
|---|---|---|
作用域 | 文件内全局 | 文件内全局 |
生命周期 | 程序运行期间 | 程序运行期间 |
链接性 | 外部链接 | 内部链接 |
跨文件访问 | 可以 | 不可以 |

全局函数:是在所有函数外部定义的函数。(默认的函数类型)
全局函数的特点:
全局可见性:全局函数具有全局的作用域。 外部链接属性:全局函数具有外部链接属性。 代码示例:全局函数
//file1.c
#include <stdio.h>
// 全局函数的定义
int subtract(int a, int b)
{
return a - b;
}//file2.c
#include <stdio.h>
// 声明 file1.c 中的全局函数
extern int subtract(int a, int b);
int main()
{
int result = subtract(8, 3);
printf("两数相减的结果: %d\n", result); //输出:两数相减的结果: 5
return 0;
}extern关键字,但extern可以省略,因为函数声明默认具有外部链接属性
extern关键字:用于声明一个在其他文件中定义的全局函数。
extern声明不会定义函数,只是引用已经存在的全局函数。静态函数:是使用static关键字修饰的函数,其定义形式和普通函数类似,只是在函数返回类型前加上static关键字。
静态函数的特点:
局部可见性:静态函数的作用域仅限于定义它的源文件。
内部链接属性:静态函数具有内部链接属性。
代码示例:静态函数
// file1.c
#include <stdio.h>
// 静态函数定义
static void staticFunc()
{
printf("这是 file1.c 中的静态函数\n");
}
//全局函数定义
void publicFunc()
{
printf("从同一个文件中调用静态函数:\n");
staticFunc(); // 静态函数只能在当前文件中调用
}// file2.c
// 声明全局函数
void publicFunc();
int main()
{
publicFunc(); // 调用全局函数
// staticFunc(); // 错误:静态函数无法在其他文件中访问
return 0;
}输出:
从同一个文件中调用静态函数: 这是 file1.c 中的静态函数
全局函数和静态函数的对比:
特性 | 全局函数 | 静态函数 |
|---|---|---|
作用域 | 整个程序 | 定义它的文件 |
链接属性 | 外部链接(external linkage) | 内部链接(internal linkage) |
可见性 | 所有文件可见 | 仅定义文件可见 |
关键字 | 无(默认) | static |
用途 | 提供公共接口 | 隐藏实现细节,模块化设计 |
在 Windows 下,程序是一个普通的可执行文件,以下列出一个二进制可执行文件的基本情况:

通过上图可以得知:
程序没有运行时,也就是说程序没有加载到内存时,可执行程序内部已经分好3段信息 分别为:代码区(text)、数据区(data)和未初始化数据区(bss)(有些人直接把data和bss合起来叫做全局区或静态区)
程序没有运行时,代码区 和 全局区( data 和 bss ) 的大小就是固定的,程序运行期间不能改变。
然后,程序运行时,系统把程序加载到内存,内存上除了有根据可执行程序的信息分出代码区(text)、数据区(data) 和 未初始化数据区(bss)之外,还额外增加了 栈区、堆区

在C语言中,程序运行时使用的内存通常分为以下几个分区(也称为内存段)
C语言程序的内存通常分为以下四个主要区域:
初始化的全局变量和静态变量(Data 段)未初始化的全局变量和静态变量(Bss段)代码区(Text Segment)
全局区/静态区(Data Segment)
全局区/静态区分为两部分:
初始化的全局变量和静态变量(初始化区)
存放内容:显式初始化的全局变量和静态变量。
特点:
示例:
int globalVar = 10; // 初始化的全局变量
static int staticVar = 20; // 初始化的静态变量未初始化的全局变量和静态变量(BSS区)
存放内容:未显式初始化的全局变量和静态变量。
特点:
示例:
int globalVar; // 未初始化的全局变量
static int staticVar; // 未初始化的静态变量栈区(Stack Segment)
存放内容:
特点:
示例:
void func()
{
int localVar = 30; // 局部变量,存放在栈区
printf("%d\n", localVar);
}堆区(Heap Segment)
存放内容:动态分配的内存(通过malloc、calloc、realloc等函数分配)
特点:
free函数),否则会导致内存泄漏。示例:
int *ptr = (int *)malloc(sizeof(int) * 10); // 动态分配内存,存放在堆区
if (ptr != NULL)
{
free(ptr); // 释放内存
}内存分区示意图:
+-----------------------+
| 栈区 (Stack) | <- 高地址
|-----------------------|
| ↓ |
| |
| ↑ |
|-----------------------|
| 堆区 (Heap) |
|-----------------------|
| 未初始化数据区 (BSS) |
|-----------------------|
| 初始化数据区 (Data) |
|-----------------------|
| 代码区 (Text) | <- 低地址
+-----------------------+内存四区的比较:
分区 | 存放内容 | 管理方式 | 生命周期 | 特点 |
|---|---|---|---|---|
代码区 | 程序指令常量字符串 | 操作系统 | 整个程序运行期间 | 只读、大小固定 |
全局区/静态区 | 初始化和未初始化的全局变量/静态变量 | 编译器 | 整个程序运行期间 | 初始化区存放显式初始化的变量BSS区存放未初始化的变量 |
栈区 | 局部变量函数调用上下文 | 编译器 | 函数调用期间 | 自动管理、大小有限 |
堆区 | 动态分配的内存 | 程序员 | 直到显式释放 | 手动管理、大小较大 |
代码示例:
#include <stdio.h>
#include <stdlib.h>
int globalVar = 10; // 全局变量,存放在全局区/静态区
static int staticVar = 20; // 静态变量,存放在全局区/静态区
int uninitGlobalVar; // 未初始化的全局变量,存放在BSS段
void func()
{
int localVar = 30; // 局部变量,存放在栈区
static int staticLocalVar = 40;// 静态局部变量,存放在全局/静态区
int* heapVar = (int*)malloc(sizeof(int)); // 动态分配内存,存放在堆区
*heapVar = 50;
printf("局部变量: % d\n静态局部变量: % d\n堆变量: % d\n", localVar, staticLocalVar, *heapVar);
free(heapVar);
}
int main()
{
func();
return 0;
}输出:
局部变量: 30
静态局部变量: 40
堆变量: 50各存储类型的变量或函数的总结:
类型 | 存储位置 |
|---|---|
普通局部变量 | 栈区 |
静态局部变量 | 初始化在data段,未初始化在BSS段 |
普通全局变量 | 初始化在data段,未初始化在BSS段 |
静态全局变量 | 初始化在data段,未初始化在BSS段 |
全局函数 | 代码区 |
静态函数 | 代码区 |
字符串常量 | data段 |
寄存器变量 | 运行时存储在CPU寄存器 |
代码示例:各存储类型的变量所在内存分区的地址高低
#include <stdio.h>
#include <stdlib.h>
int e; //普通全局变量(未初始化)
static int f; //静态全局变量(未初始化)
int g = 10; //普通全局变量(初始化)
static int h = 10; //静态全局变量(初始化)
int main()
{
int a; //普通局部变量(未初始化)
int b = 10; //普通局部变量(初始化)
static int c; //静态局部变量(未初始化)
static int d = 10; //静态局部变量(初始化)
char i[] = "test"; //字符串常量(初始化)
char* k = NULL; //字符指针
printf("&a\t %p\t //普通局部未初始化变量\n", &a);
printf("&b\t %p\t //普通局部初始化变量\n", &b);
printf("&c\t %p\t //静态局部未初始化变量\n", &c);
printf("&d\t %p\t //静态局部初始化变量\n", &d);
printf("&e\t %p\t //普通全局未初始化变量\n", &e);
printf("&f\t %p\t //静态全局未初始化变量\n", &f);
printf("&g\t %p\t //普通全局初始化变量\n", &g);
printf("&h\t %p\t //静态全局初始化变量\n", &h);
printf("i\t %p\t //只读数据(文字常量区)\n", i);
k = (char*)malloc(10);
printf("k\t %p\t //动态分配的内存\n", k);
return 0;
}输出:
&a 000000E22E0FFCA4 //普通局部未初始化变量
&b 000000E22E0FFCC4 //普通局部初始化变量
&c 00007FF69C59D1D8 //静态局部 未 初始化变量
&d 00007FF69C59D050 //静态局部初始化变量
&e 00007FF69C59D1D0 //普通全局 未 初始化变量
&f 00007FF69C59D1D4 //静态全局 未 初始化变量
&g 00007FF69C59D048 //普通全局初始化变量
&h 00007FF69C59D04C //静态全局初始化变量
i 000000E22E0FFCE4 //只读数据(文字常量区)
k 000002C1A6090100 //动态分配的内存
内存操作函数:用于处理内存块的重要工具。
填充、复制、比较、查找 操作。
<string.h>头文件中。
内存操作函数的总结:
函数 | 功能 | 特点 |
|---|---|---|
memset | 内存填充 | 按字节将内存块置为指定值,用于初始化 |
memcpy | 内存复制 | 高效复制内存,不处理重叠情况 |
memmove | 内存移动 | 复制内存,可处理重叠区域 |
memcmp | 内存比较 | 按字节比较两块内存内容 |
memchr | 内存查找 | 在内存块中查找首个指定字节值 |
函数的介绍:memset():用于将指定内存区域的前若干个字节设置为特定的值。
<string.h> 头文件中。
memset 函数将从地址 s 开始的连续 n 个字节的内存区域,全部设置为指定的值 c
注意:
s 指向的内存区域足够大,能够容纳 n 个字节,否则会导致内存越界。c 在 unsigned char 范围内。函数的原型:
void *memset(void *s, int c, size_t n);s:是指向要操作的内存区域的指针。 c:是要设置的值。 int,但实际上会被转换为 unsigned char 类型。n:是要填充的字节数。 size_t,它是一个无符号整数类型,通常用于表示对象的大小。
函数的返回值:
memset() :返回指向目标内存区域 s 的指针,这样可以方便进行链式操作。函数的使用:
1.初始化数组
#include <stdio.h>
#include <string.h>
int main()
{
int arr[5];
// 将数组初始化为 0
memset(arr, 0, sizeof(arr)); //或者 memset(arr, 0, 5 * sizeof(int));
printf("数组的元素为: ");
for (int i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}输出:
数组的元素为: 0 0 0 0 0 2.初始化字符串
#include <stdio.h>
#include <string.h>
int main()
{
char str[10];
// 将字符数组 str 的前 9 个字节设置为 'A'
memset(str, 'A', 9);
str[9] = '\0'; // 添加字符串结束符
printf("字符串为:%s\n", str);
return 0;
}输出:
字符串为: AAAAAAAAA3.初始化结构体
#include <stdio.h>
#include <string.h>
typedef struct
{
int id;
char name[20];
} Person;
int main()
{
Person p;
// 将结构体初始化为 0
memset(&p, 0, sizeof(Person));
printf("ID: %d\n", p.id);
printf("Name: %s\n", p.name);
return 0;
}输出:
ID: 0
Name:
函数的介绍:memcpy():用于将一块内存区域的内容复制到另一块内存区域。
<string.h> 头文件中。
memmove() 函数。 memcpy 函数的作用是从源内存区域 src 开始,拷贝连续的 n 个字节到目标内存区域 dest
目标内存区域是否足够大。源和目标内存区域是否重叠。 总结:使用memcpy函数时需确保目标内存足够大,且源和目标内存不重叠。
函数的原型:
void *memcpy(void *dest, const void *src, size_t n);dest: 是指向目标内存区域的指针,即数据要被复制到的地方。 src:是指向源内存区域的指针,即数据的来源。n:是要复制的字节数。 size_t(无符号整数类型),它指定了从源内存区域复制到目标内存区域的数据量。
函数的返回值:memcpy():返回指向目标内存区域dest的指针,这样可以方便进行链式操作,比如连续调用多个内存操作函数。
函数的使用:
1.复制整型数组
#include <stdio.h>
#include <string.h>
int main()
{
int src[] = { 1, 2, 3, 4, 5 };
int dest[5];
// 复制整型数组
memcpy(dest, src, sizeof(src));
printf("源数组: ");
for (int i = 0; i < 5; i++)
{
printf("%d ", src[i]);
}
printf("\n");
printf("目标数组: ");
for (int i = 0; i < 5; i++)
{
printf("%d ", dest[i]);
}
printf("\n");
return 0;
}输出:
源数组: 1 2 3 4 5
目标数组: 1 2 3 4 52.复制字符串
#include <stdio.h>
#include <string.h>
int main()
{
char src[] = "Hello, World!";
char dest[20];
// 复制字符串(包括 '\0')
memcpy(dest, src, strlen(src) + 1);
printf("源字符串: %s\n", src);
printf("目标字符串: %s\n", dest);
return 0;
}输出:
源字符串: Hello, World!
目标字符串: Hello, World!3.复制结构体
#include <stdio.h>
#include <string.h>
typedef struct
{
int id;
char name[20];
} Person;
int main()
{
Person src = { 1, "Alice" };
Person dest;
// 复制结构体
memcpy(&dest, &src, sizeof(Person));
printf("源结构体: ID = %d, Name = %s\n", src.id, src.name);
printf("目标结构体: ID = %d, Name = %s\n", dest.id, dest.name);
return 0;
}输出:
源结构体: ID = 1, Name = Alice
目标结构体: ID = 1, Name = Alice
函数的介绍:memmove():用于将一块内存的内容复制到另一块内存中。
<string.h> 头文件中。memcpy() 不同的是,memmove() 能够正确处理源内存区域和目标内存区域重叠的情况。 函数的原型:
void *memmove(void *dest, const void *src, size_t n);dest:是指向目标内存区域的指针,即数据要被复制到的位置。 src:是指向源内存区域的指针,即数据的来源。n:是要复制的字节数。 size_t(无符号整数类型),它明确了从源内存区域复制到目标内存区域的数据量。
函数的返回值:memmove():返回指向目标内存区域dest的指针,这样便于进行链式操作。
函数的使用:
内存无重叠情况:
1.复制整型数组
#include <stdio.h>
#include <string.h>
int main()
{
int src[] = { 1, 2, 3, 4, 5 };
int dest[5];
// 复制整型数组
memmove(dest, src, sizeof(src));
printf("源数组: ");
for (int i = 0; i < 5; i++)
{
printf("%d ", src[i]);
}
printf("\n");
printf("目标数组: ");
for (int i = 0; i < 5; i++)
{
printf("%d ", dest[i]);
}
printf("\n");
return 0;
}输出:
源数组: 1 2 3 4 5
目标数组: 1 2 3 4 52.复制字符串
#include <stdio.h>
#include <string.h>
int main()
{
char src[] = "Hello, World!";
char dest[20];
// 复制字符串(包括 '\0')
memmove(dest, src, strlen(src) + 1);
//或者这样写:memmove(dest, src, sizeof(src));
printf("源字符串: %s\n", src);
printf("目标字符串: %s\n", dest);
return 0;
}输出:
源字符串: Hello, World!
目标字符串: Hello, World!3.复制结构体
#include <stdio.h>
#include <string.h>
typedef struct
{
int id;
char name[20];
} Person;
int main()
{
Person src = { 1, "Alice" };
Person dest;
// 复制结构体
memmove(&dest, &src, sizeof(Person));
printf("源结构体: ID = %d, Name = %s\n", src.id, src.name);
printf("目标结构体: ID = %d, Name = %s\n", dest.id, dest.name);
return 0;
}输出:
源结构体: ID = 1, Name = Alice
目标结构体: ID = 1, Name = Alice内存无重叠情况:
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "Hello, World!";
// 将字符串移动到自身内部
memmove(str + 7, str, 13);
printf("结果为: %s\n", str);
return 0;
}程序崩溃!!!
错误分析:
char str[] = "Hello, World!";
memmove(str + 7, str, 14); // 将字符串移动到自身内部这两行代码尝试将 str 字符串的前 13 个字节复制到从 str + 7 开始的位置。
但是:
str 数组的大小是由初始化字符串 "Hello, World!" 决定的,该字符串包含 14个字符(包括字符串结束符 '\0'),数组 str 的大小也是 14 个字节。
memmove(str + 7, str, 13); 时,试图从 str 复制 14个字节到从 str + 7 开始的位置,这会导致复制操作超出 str 数组的边界,从而引发未定义行为。
str 数组之外的内存区域,可能会破坏其他重要的数据。修正建议:
如果想要实现字符串内部的移动操作,需要确保目标区域有足够的空间来容纳复制的数据。可以将 str 数组的大小扩大,以避免内存越界问题。
#include <stdio.h>
#include <string.h>
int main()
{
// 扩大数组大小以避免越界
char str[21] = "Hello, World!";
// 将字符串移动到自身内部
memmove(str + 7, str, 14);
printf("结果为: %s\n", str);
return 0;
}输出:
结果为: Hello, Hello, World!memmove() 与 memcpy() 的区别:
特性 | memcpy() | memmove() |
|---|---|---|
内存重叠 | 不处理内存重叠,行为未定义 | 处理内存重叠,保证数据正确 |
性能 | 通常更快(假设没有内存重叠) | 稍慢(需要额外检查内存重叠) |
使用场景 | 源和目标内存不重叠时使用 | 源和目标内存可能重叠时使用 |
函数的介绍:memcmp():用于按字节顺序比较两块内存区域的内容。
<string.h> 头文件中。memcmp 函数是按字节比较的,不关心数据的类型。 memcmp 函数逐字节比较两个内存区域的前 n 个字节,直到找到不相等的字节或比较完所有字节。
unsigned char)函数的原型:
int memcmp(const void *s1, const void *s2, size_t n);s1:是指向第一个要比较的内存区域的指针。s2:是指向第二个要比较的内存区域的指针。n:是要比较的字节数。 size_t(无符号整数类型),它明确了从两个内存块起始位置开始比较的字节数量。
函数的返回值:s1所指向的内存块的内容在字典序上 与s2所指向的内存块的内容
s1 < s2,返回负值s1 == s2,返回 0s1 > s2,返回正值函数的使用:
1.比较整型数组
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1, 2, 3, 4, 5 };
int arr2[] = { 1, 2, 3, 4, 5 };
int arr3[] = { 1, 2, 3, 4, 6 };
// 比较整型数组
int result1 = memcmp(arr1, arr2, sizeof(arr1)); // 比较整个数组
int result2 = memcmp(arr1, arr3, sizeof(arr1)); // 比较整个数组
printf("Result1: %d\n", result1); // 输出 0(相等)
printf("Result2: %d\n", result2); // 输出负值(arr1 < arr3)
return 0;
}输出:
Result1: 0
Result2: -12.比较字符串
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "Hello";
char str2[] = "Hello";
char str3[] = "World";
// 比较字符串
int result1 = memcmp(str1, str2, 5); // 比较前5个字节
int result2 = memcmp(str1, str3, 5); // 比较前5个字节
printf("Result1: %d\n", result1); // 输出 0(相等)
printf("Result2: %d\n", result2); // 输出负值(str1 < str3)
return 0;
}输出:
Result1: 0
Result2: -13.比较结构体
#include <stdio.h>
#include <string.h>
typedef struct
{
int id;
char name[20];
} Person;
int main()
{
Person p1 = { 1, "Alice" };
Person p2 = { 1, "Alice" };
Person p3 = { 2, "Bob" };
// 比较结构体
int result1 = memcmp(&p1, &p2, sizeof(Person)); // 比较整个结构体
int result2 = memcmp(&p1, &p3, sizeof(Person)); // 比较整个结构体
printf("Result1: %d\n", result1); // 输出 0(相等)
printf("Result2: %d\n", result2); // 输出负值(p1 < p3)
return 0;
}输出:
Result1: 0
Result2: -1
函数的介绍:malloc():用于在运行时从堆(heap)中申请一块指定大小的内存空间。
<stdlib.h> 头文件中。注意事项:
malloc() 会根据传入的大小分配一块连续的内存区域。void*,需要根据实际用途进行类型转换。malloc() 分配的内存不会自动初始化,内容可能是随机的。(一般使用memset初始化)NULL,使用前应检查指针是否为 NULL。函数的原型:
void* malloc(size_t size);size:要分配的内存块的大小。 size_t(无符号整数类型),你需要根据实际需求指定所需内存的字节数。
函数的返回值:
void *,意味着可以将其转换为任意类型的指针。NULL函数的使用:
1.分配整数数组
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int count, *array, n;
printf("请输入要申请数组的个数:\n");
scanf("%d", &n);
array = (int*)malloc(n * sizeof(int));
if (array == NULL)
{
printf("申请空间失败!\n");
return -1;
}
//将申请到空间清0
memset(array, 0, sizeof(int) * n);
for (count = 0; count < n; count++) /*给数组赋值*/
{
array[count] = count;
}
for (count = 0; count < n; count++) /*打印数组元素*/
{
printf("%2d", array[count]);
}
free(array);
array = NULL;
return 0;
}2.分配结构体
#include <stdio.h>
#include <stdlib.h>
typedef struct
{
int id;
char name[20];
} Person;
int main()
{
// 分配能存储一个 Person 结构体的内存空间
Person* p = (Person*)malloc(sizeof(Person));
if (p == NULL)
{
printf("内存分配失败\n");
return 1;
}
// 初始化结构体成员
p->id = 1;
strcpy(p->name, "Alice");
// 输出结构体信息
printf("ID: %d, Name: %s\n", p->id, p->name);
// 释放分配的内存
free(p);
// 将指针置为 NULL,避免悬空指针
p = NULL;
return 0;
}
函数的介绍:free():用于释放动态分配的内存,防止内存泄漏。
<stdlib.h> 头文件中。 free() 函数的主要功能是将之前动态分配的内存块归还给操作系统,使得这部分内存可以被后续的内存分配请求再次使用。
释放内存可以避免内存泄漏,即:程序占用了不再使用的内存,导致系统可用内存逐渐减少。
函数的原型:
void free(void* ptr);ptr:是指向需要释放的内存块的指针。 malloc()、calloc() 或 realloc() 返回的指针。ptr 为 NULL,则函数不执行任何操作。函数的使用:
注意事项:
避免野指针:释放内存后,指针仍然指向原来的地址,但该地址已无效。如果不将指针置为 NULL,可能会导致野指针问题。
free(ptr);
ptr = NULL; // 推荐做法不能释放栈内存:free() 只能释放堆内存,不能释放栈上的变量。
int x = 10;
free(&x); // 错误!x 是栈上的变量不能重复释放:对同一块内存多次调用 free() 会导致程序崩溃。
int *ptr = (int*)malloc(sizeof(int));
free(ptr);
free(ptr); // 错误!重复释放释放空指针是安全的:如果指针是 NULL,free() 不会执行任何操作。
int *ptr = NULL;
free(ptr); // 安全,不会报错内存泄漏:如果动态分配的内存没有通过 free() 释放,会导致内存泄漏。
int *ptr = (int*)malloc(sizeof(int));
// 忘记调用 free(ptr);#include <stdio.h>
int *fun()
{
int a = 10;
return &a;//函数调用完毕,a释放
}
int main(int argc, char *argv[])
{
int *p = NULL;
p = fun();
*p = 100; //操作野指针指向的内存,err
return 0;
}#include <stdio.h>
int *fun()
{
static int a = 10;
return &a; //函数调用完毕,a不释放
}
int main(int argc, char *argv[])
{
int *p = NULL;
p = fun();
*p = 100; //ok
printf("*p = %d\n", *p);
return 0;
}#include <stdio.h>
#include <stdlib.h>
void fun(int *tmp)
{
tmp = (int *)malloc(sizeof(int));
*tmp = 100;
}
int main(int argc, char *argv[])
{
int *p = NULL;
fun(p); //值传递,形参修改不会影响实参
printf("*p = %d\n", *p);//err,操作空指针指向的内存
return 0;
}这段代码的目的是通过函数 fun 动态分配内存并修改指针 p 指向的值。
但由于函数参数传递的方式问题,代码中存在错误。
程序崩溃!!!
(程序崩溃的主要原因是我们对空指针进行解引用的操作)
代码示例:
#include <stdio.h>
#include <stdlib.h>
void fun(int* tmp)
{
tmp = (int*)malloc(sizeof(int));
*tmp = 100;
}
int main(int argc, char* argv[])
{
int* p = NULL;
int a = 10;
p = &a;
fun(p); //值传递,形参修改不会影响实参
printf("*p = %d\n", *p);//err,操作空指针指向的内存
return 0;
}输出:
*p = 10失败分析:(为什么fun函数不能修改p指向的值)
fun(p) 传递的是指针 p 的值(即: NULL),而不是指针 p 本身。fun 函数内部,tmp 是一个局部变量,它的修改不会影响 main 函数中的 pp 在 main 函数中仍然是 NULLmain 函数中,p 仍然是 NULL,而 printf("*p = %d\n", *p); 试图解引用空指针,这是未定义行为,通常会导致程序崩溃。修正方法:
方法 1:传递指针的指针
p 的地址传递给 fun 函数,从而在 fun 中修改 p 的值。#include <stdio.h>
#include <stdlib.h>
void fun(int** tmp)
{
*tmp = (int*)malloc(sizeof(int)); //---> p =
**tmp = 100; //---> *p =
}
int main(int argc, char* argv[])
{
int* p = NULL;
fun(&p); // 传递 p 的地址
printf("*p = %d\n", *p); // 正确输出 100
// 释放动态分配的内存
free(p);
return 0;
}输出:
*p = 100方法 2:返回动态分配的指针
fun 函数返回动态分配的指针,并在 main 函数中接收返回值。#include <stdio.h>
#include <stdlib.h>
int* fun()
{
int* tmp = (int*)malloc(sizeof(int));
*tmp = 100;
return tmp;
}
int main(int argc, char* argv[])
{
int* p = NULL;
p = fun(); // 接收返回值
printf("*p = %d\n", *p); // 正确输出 100
// 释放动态分配的内存
free(p);
return 0;
}输出:
*p = 100总结:
代码中的问题是由于函数参数按值传递导致的,fun 函数内部的修改不会影响 main 函数中的 p
修正方法有两种:
int **tmp),在 fun 中修改 p 的值。fun 函数返回动态分配的指针,并在 main 中接收返回值。#include <stdio.h>
#include <stdlib.h>
void fun(int *tmp)
{
*tmp = 100;
}
int main(int argc, char *argv[])
{
int *p = NULL;
p = (int *)malloc(sizeof(int));
fun(p); // 值传递
printf("*p = %d\n", *p); // ok,*p为100
// 释放动态分配的内存
free(p);
return 0;
}这段代码的目的是通过函数 fun 修改指针 p 指向的值。
与之前的代码不同,这段代码是正确的,并且能够正常运行。
输出:
*p = 100疑问:上面的两个代码示例都是值传递,但是为什么一个fun函数可以修改成功,而另一个不可以修改的情况呢?
失败原因分析:
main 函数中调用 fun(p) 时,p 的值(即:变量 a 的地址)被复制给了形参 tmp。此时,p 和 tmp 都指向变量 afun 函数中,tmp = (int*)malloc(sizeof(int)); 这行代码将 tmp 指向了新分配的一块动态内存,而不再指向变量 a。然后 *tmp = 100; 是将新分配内存中的值设置为 100tmp 的改变不会影响 p,p 仍然指向变量 a。所以 printf("*p = %d\n", *p); 输出的是变量 a 的值,而不是新分配内存中的值成功原因分析:
main 函数中调用 fun(p) 时,p 的值(即:动态分配内存的地址)被复制给了形参 tmp。此时,p 和 tmp 都指向同一块动态分配的内存fun 函数中,*tmp = 100; 是对 tmp 所指向的内存中的值进行修改,也就是修改了动态分配内存中的值。tmp 是 p 的副本,但它们指向同一块内存。所以对 *tmp 的修改实际上就是对 *p 的修改。因此,printf("*p = %d\n", *p); 输出的是修改后的值 100。 总结:
第一个代码中 fun 函数修改的是形参 tmp 本身的指向,而不是 tmp 所指向的内存中的值,由于值传递机制,形参的改变不会影响实参
第二个代码中 fun 函数修改的是形参 tmp 所指向的内存中的值,因为 p 和 tmp 指向同一块内存,所以修改操作会反映到 p 所指向的内存中
#include <stdio.h>
#include <stdlib.h>
int* fun()
{
int* tmp = NULL;
tmp = (int*)malloc(sizeof(int));
*tmp = 100;
return tmp;//返回堆区地址,函数调用完毕,不释放
}
int main(int argc, char* argv[])
{
int* p = NULL;
p = fun();
*p = 200; //ok
printf("*p = %d\n", *p);
//堆区空间,使用完毕,手动释放
if (p != NULL)
{
free(p);
p = NULL;
}
return 0;
}👨💻 博主正在持续更新C语言基础系列中。 ❤️ 如果你觉得内容还不错,请多多点赞。 ⭐️ 如果你觉得对你有帮助,请多多收藏。(防止以后找不到了) 👨👩👧👦
C语言基础系列持续更新中~,后续分享内容主要涉及C++全栈开发的知识,如果你感兴趣请多多关注博主。