局部变量 — 栈区
全局变量 — 静态区

栈区 — 局部变量,函数的形参
堆区 — 动态内存分配
静态区 — 全局变量,静态变量(static)

内存的使用方式:

  1. 新建一个变量
    int a = 1;//局部变量,栈区
    int global_a = 1;//全局变量,静态区
  2. 新建一个数组
    int a[10];//与变量的内存相同,只是数组申请一块连续内存
1
2
3
4
5
6
7
8
9
10
11
12
13
struct S  
{
char name[20];
int age;
};

int main(void)
{
struct S arr[50];//50个struct S类型的数据
//30 --- 浪费
//60 --- 不够
return 0;
}

C语言可以创建变长数组 — C99标准中加入了,但不一定所有编译器都支持(比如说VS)
gcc支持C99

动态内存分配

malloc和free都定义在是stdlib.h中,也可以用malloc.h中但建议用malloc.h

1. malloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(void)  
{
//向内存中申请10个整型的空间
int* p = (int*)malloc(10 * sizeof(int));
//malloc返回一个void型的指针,没有足够空间时返回NULL
//gcc编译器不需要强制类型转换,msvc可能会报警告
if (p == NULL)
{
//打印错误原因
printf("%s\n", strerror(errno));
}
else
{
//正常使用空间
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
printf("%d ", *(p + i));
}
}
return 0;
}

输出为0 1 2 3 4 5 6 7 8 9
C语言标准未定义size为0的情况,取决于编译器,所以应避免

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(void)  
{
//向内存中申请INT_MAX个整型的空间
int* p = (int*)malloc(INT_MAX);
//malloc返回一个void型的指针,没有足够空间时返回NULL
//gcc编译器不需要强制类型转换,msvc可能会报警告
if (p == NULL)
{
//打印错误原因
printf("%s\n", strerror(errno));
}
return 0;
}

输出为Not enough space

2. free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int main(void)  
{
//向内存中申请10个整型的空间
int* p = (int*)malloc(10 * sizeof(int));
//malloc返回一个void型的指针,没有足够空间时返回NULL
//gcc编译器不需要强制类型转换,msvc可能会报警告
if (p == NULL)
{
//打印错误原因
printf("%s\n", strerror(errno));
}
else
{
//正常使用空间
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
printf("%d ", *(p + i));
}
}
//当申请的动态内存不再使用时,应该还给操作系统
free(p);
p = NULL;
//p指向的这部分空间被释放了,但是p指针依然存在,
//因此应该把p指针赋值为空指针来防止出现意外断开联系
//malloc和free应成对使用
return 0;
}

如果free参数ptr是NULL,则函数什么事都不做
如果ptr指向的空间不是动态开辟的,那么free的行为是未定义的

3. calloc

开辟一块空间并把每个元素初始化为0,而malloc是随机值但每个元素是固定的随机值

1
2
3
4
5
6
int main(void)  
{
//malloc(10 * sizeof(int));
int* p = calloc(10, sizeof(int));
return 0;
}

4. realloc

如果要增加空间:
若后面的空间足够增加,那么在原来的基础上增加空间,返回原地址
如果后面剩余的空间不够增加,则另外开辟一块新的空间,原空间的数据移到新空间中, 释放旧的内存空间 ,返回新空间的地址
如果增加的空间过大,超出限制导致开辟失败,返回NULL,原空间不变,不能用原空间的地址来接受,不然会丢失原空间的地址
总之不要用原来的指针来接收realloc的返回值

realloc如果参数是NULL,则和malloc功能相同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int main(void)  
{
int* p = (int*)malloc(20);
if (p == NULL)
{
printf("%s\n", strerror(errno));
}
else
{
for (int i = 0; i < 5; i++)
{
*(p + i) = i;
printf("%d ", *(p + i));
}
}
//在这里使用malloc开辟的20个字节的内存
//不够用,想要增加20个字节变成40个字节
//使用realloc
int* ptr = realloc(p, 40);
if (ptr != NULL)
{
p = ptr;
}
for (int i = 5; i < 10; i++)
{
*(p + i) = i;
printf("%d ", *(p + i));
}
free(p);
p = NULL;
return 0;
}

常见的动态内存错误

  • 对NULL进行解引用操作
    在开辟动态内存后一定要检查是否返回NULL,否则直接对NULL操作会出现问题
1
2
3
4
5
6
7
8
9
10
11
int main(void)  
{
int* p = (int*)malloc(20);
//这里没有检查是否为空指针
for (int i = 0; i < 5; i++)
{
*(p + i) = i;
printf("%d ", *(p + i));
}
return 0;
}
  • 对动态开辟内存的越界访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main(void)  
{
int* p = (int*)malloc(20);
if (p == NULL)
{
printf("%s\n", strerror(errno));
}
else
{
for (int i = 0; i < 10; i++)//这里越界访问
{
*(p + i) = i;
printf("%d ", *(p + i));
}
}
free(p);
p = NULL;
return 0;
}
  • 对非动态开辟内存使用free
1
2
3
4
5
6
7
8
9
int main(void)  
{
int a = 10;
int* p = &a;
*p = 20;
free(p);//对非动态开辟内存使用free
p = NULL;
return 0;
}
  • 使用free释放动态内存的一部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(void)  
{
int* p = malloc(40);
if (p == NULL)
{
return 0;
}
for (int i = 0; i < 10; i++)
{
*p++ = i;
}
free(p);//此时p地址不是初始值
p = NULL;
return 0;
}
  • 对同一块动态内存多次释放
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(void)  
{
int* p = malloc(40);
if (p == NULL)
{
return 0;
}
//使用
//释放
free(p);
//...
free(p);
return 0;
}

解决方法:free后立即将p置为空指针,这样即使多次释放后面的释放也没有意义

  • 动态开辟内存忘记释放(内存泄漏)
1
2
3
4
5
6
7
8
int main(void)  
{
while(1)
{
malloc(1000);
}
return 0;
}

练习题

1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void GetMemory(char *p)
{
p = (char*)malloc(100);
}

void test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}

int main(void)
{
test();
return 0;
}

问题:
1.str以值传递给p,p只是str的一个临时拷贝,改变p不会改变str,因此str一直是NULL
2.strcopy接收到str地址是NULL,会发生程序崩溃
3.动态开辟的内存没有释放,造成内存泄漏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//改正1
void GetMemory(char **p)
{
*p = (char*)malloc(100);
}

void test(void)
{
char* str = NULL;
GetMemory(&str);
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
}

int main(void)
{
test();
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//改正2
char* GetMemory(char *p)
{
p = (char*)malloc(100);
return p;
}

void test(void)
{
char* str = NULL;
str = GetMemory(str);
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
}

int main(void)
{
test();
return 0;
}

2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
char* GetMemory(void) 
{
char p[] = "hello world";
return p;
}

void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}

int main(void)
{
Test(void);
return 0;
}

返回栈空间地址的问题
返回p的地址,但里面的内容已经被销毁

3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void GetMemory(char **p, int num) 
{
*p = malloc(num);
}

void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello world");
printf(str);
}

int main(void)
{
Test();
return 0;
}

问题:忘记释放动态开辟的内存,导致内存泄漏

4.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Test(void) 
{
char *str = (char *)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}

int main(void)
{
Test();
return 0;
}

问题:free释放str指向的空间后并不会把str置为NULL,后面再调用就是非法访问

C/C++内存的开辟

  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结 束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是 分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等
  2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分 配方式类似于链表
  3. 数据段(静态区):(static)存放全局变量、静态数据。程序结束后由系统释放
  4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码

柔性数组

C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct S  
{
int n;
int arr[];//未知大小的 --- 柔性数组成员 --- 数组大小是可以调整的
};
//或者
//struct S
//{
// int n;
// int arr[0];
//};
int main(void)
{
struct S s;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct S  
{
int n;
int arr[];
};

int main(void)
{
int i;
struct S* ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int));
ps->n = 100;
for (i = 0; i < 5; ++i)
{
ps->arr[i] = i;
}
struct S* ptr = realloc(ps, 44);
if (ptr != NULL)
{
ps = ptr;
}
for (i = 5; i < 10; i++)
{
ps->arr[i] = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", ps->arr[i]);
}
free(ps);
ps = NULL;
return 0;
}

下面这种可以实现和柔性数组相同的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
struct S  
{
int n;
int* arr;
};

int main(void)
{
struct S* ps = (struct S*)malloc(sizeof(struct S));
ps->arr = malloc(5 * sizeof(int));
int i = 0;
for (i = 0; i < 5; i++)
{
ps->arr[i] = i;
}
for (i = 0; i < 5; i++)
{
printf("%d ", ps->arr[i]);
}
//调整空间
int* ptr = realloc(ps->arr, 10 * sizeof(int));
if (ptr != NULL)
{
ps->arr = ptr;
}
for (i = 5; i < 10; i++)
{
ps->arr[i] = i;
}
for (i = 5; i < 10; i++)
{
printf("%d ", ps->arr[i]);
}
free(ps->arr);
ps->arr = NULL;
free(ps);
ps = NULL;
return 0;
}

柔性数组对比这种方案的优点:

  1. malloc只有一次
  2. 内存利用率高,内存碎片少
  3. 柔性数组中结构体所有成员的内存是连续的,访问效率更高