OSTEP:系统API的调用
第十四章:插叙:内存操作API
执行
null
文件后并没有提示或报错,代码如下1
2
3
4
5
6int main()
{
int *pt = NULL;
free(pt); // 释放空指针
return 0;
}在执行完
gdb null
后的run
后,提示了以下信息log
1
2
3(gdb) run
Starting program: /home/halc/code/cpp/null
[Inferior 1 (process 9285) exited normally]在使用
valgrind
检查后,可以得到以下输出信息log
1
2
3
4
5
6
7
8
9
10
11
12
13
14==9579== Memcheck, a memory error detector
==9579== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==9579== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==9579== Command: ./null
==9579==
==9579==
==9579== HEAP SUMMARY:
==9579== in use at exit: 0 bytes in 0 blocks
==9579== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==9579==
==9579== All heap blocks were freed -- no leaks are possible
==9579==
==9579== For lists of detected and suppressed errors, rerun with: -s
==9579== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)根据C/C++ Preference上
free()
的介绍如下If is a null pointer, the function does nothing. ptr
当指针是空指针的时候,啥都不会发生,因此也理所当然的没有发生任何的内存泄露首先使用
malloc()
函数对内存空间进行分配,但是不使用free()
对内存进行释放1
2
3
4
5
6int main()
{
int *pt = (int *)malloc(sizeof(int));
// free(pt);
return 0;
}使用
gdb
对可执行文件进行调试如下gdb log
1
2Starting program: /home/halc/code/cpp/null
[Inferior 1 (process 9978) exited normally]使用
valgrind
对可执行文件调试入如下valgrind log
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24$ valgrind --leak-check=yes ./null
==9930== Memcheck, a memory error detector
==9930== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==9930== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==9930== Command: ./null
==9930==
==9930==
==9930== HEAP SUMMARY:
==9930== in use at exit: 4 bytes in 1 blocks
==9930== total heap usage: 1 allocs, 0 frees, 4 bytes allocated
==9930==
==9930== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==9930== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==9930== by 0x10915E: main (null.cpp:6)
==9930==
==9930== LEAK SUMMARY:
==9930== definitely lost: 4 bytes in 1 blocks
==9930== indirectly lost: 0 bytes in 0 blocks
==9930== possibly lost: 0 bytes in 0 blocks
==9930== still reachable: 0 bytes in 0 blocks
==9930== suppressed: 0 bytes in 0 blocks
==9930==
==9930== For lists of detected and suppressed errors, rerun with: -s
==9930== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)这个时候通过
valgrind
工具即可检查出,在堆空间中有definitely lost(确定的内存泄露)
为4 bytes in 1 blocks
,正好对应了代码中sizeof(int)
的大小和数量首先使用
malloc()
函数分配100个int
的空间给指针data
,然后在data[100]
的位置赋值1
2
3
4
5
6
7int main()
{
int *data = (int *)malloc(sizeof(int) * 100);
data[100] = 0;
free(data);
return 0;
}程序直接运行的时候不会报错,没有任何提示。但是使用
valgrind
进行检查的时候有如下日志log
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21$ valgrind --leak-check=yes ./null
==10500== Memcheck, a memory error detector
==10500== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==10500== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==10500== Command: ./null
==10500==
==10500== Invalid write of size 4
==10500== at 0x10918D: main (null.cpp:7)
==10500== Address 0x4a4c1d0 is 0 bytes after a block of size 400 alloc'd
==10500== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==10500== by 0x10917E: main (null.cpp:6)
==10500==
==10500==
==10500== HEAP SUMMARY:
==10500== in use at exit: 0 bytes in 0 blocks
==10500== total heap usage: 1 allocs, 1 frees, 400 bytes allocated
==10500==
==10500== All heap blocks were freed -- no leaks are possible
==10500==
==10500== For lists of detected and suppressed errors, rerun with: -s
==10500== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)可以看到
valgrind
成功检测出一个无效的写入,在内存分配之后,只有data[0] - data[99]
是可以正常写入的,data[100]
并不存在,因此出现了无效写入的错误用和第五题相似的方法创建一个数组,然后通过
free()
函数对内存释放,然后对已经释放的空间进行读取1
2
3
4
5
6
7int main()
{
int *data = (int *)malloc(sizeof(int) * 100);
free(data);
printf("%d\n", data[0]);
return 0;
}在本地电脑上直接运行的时候输出了
0
,使用valgrind
工具进行检查的时候输出了如下日志log
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24==10682== Memcheck, a memory error detector
==10682== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==10682== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==10682== Command: ./null
==10682==
==10682== Invalid read of size 4
==10682== at 0x1091B3: main (null.cpp:8)
==10682== Address 0x4a4c040 is 0 bytes inside a block of size 400 free'd
==10682== at 0x483CA3F: free (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==10682== by 0x1091AE: main (null.cpp:7)
==10682== Block was alloc'd at
==10682== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==10682== by 0x10919E: main (null.cpp:6)
==10682==
0
==10682==
==10682== HEAP SUMMARY:
==10682== in use at exit: 0 bytes in 0 blocks
==10682== total heap usage: 2 allocs, 2 frees, 1,424 bytes allocated
==10682==
==10682== All heap blocks were freed -- no leaks are possible
==10682==
==10682== For lists of detected and suppressed errors, rerun with: -s
==10682== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)可以看见检测出了无效的读写
Invalid read of size 4
对
data
数组中的data[50]
进行内存释放,然后输出data[0]
1
2
3
4
5
6
7
8int main()
{
int *data = (int *)malloc(sizeof(int) * 100);
// free(data);
free(data + 50);
printf("%d\n", data[0]);
return 0;
}在直接运行的时候会直接报错
1
2
3$ ./null
free(): invalid pointer
[1] 10986 abort ./null不需要用
valgrind
也可以检查出此处有free()
的问题根据 c-free-invalid-pointer 的回答,可以知道
When you have in fact allocated a block of memory, you can only free it from the pointer returned by. That is to say, only from the beginning of the block.
当我们分配了一块内存的时候,我们只能从返回的指针开始对这块内存进行释放,也就是说我们只能从内存块的开头对某一处内存进行释放
通过
realloc()
函数实现一个类似vector
的操作1
2
3
4
5
6
7
8
9
10int main()
{
int *pt = (int *)malloc(sizeof(int)); // 给指针pt分配1个int大小空间的内存
*pt = 10; // 对对应内存位置进行赋值
pt = (int *)realloc(pt, sizeof(int) * 2); // 将原本的变量扩容为2个int大小空间
pt[1] = 20; // 对新扩大的内存位置进行赋值
printf("%d, %d\n", pt[0], pt[1]); // 输出pt对应的两个内存位置的结果
free(pt); // 释放堆内存中的pt
return 0;
}程序编译可以成功通过,使用
valgrind
也正常通过,没有无效读写错误通过这种操作实现的
vector
可以在访问的时候直接通过index
进行访问,时间复杂度为O(1),并且他并不需要和链表一样创建一个指向next
的指针,不过在向后添加内容的时候依旧需要O(n)的时间复杂度来向后添加元素。顺便记录一个Tips, 在C中是允许
void *
类型进行任意转换的,因此即使没有(int *)也不会出现报错,而在C++
中对类型转换的限制更多,并不允许直接进行这样的操作,必须要进行类型转换(通过static_cast转换运算符)才能分配空间给对应的指针。偷懒,懒得写