Linux编程入门-内存管理

堆上分配

堆始于进程未初始化数据段末尾,堆的当前内存边界称为“program break”。program break位置抬升后,程序可访问新分配区域内任何内存地址,而此时物理内存页尚未分配。内核在进程首次试图访问这些虚拟内存地址时自动分配新物理内存页。系统虚拟内存页面大小可用sysconf的_SC_PAGESIZE获取。

brk/sbrk

brk将program break设为addr参数的位置,且addr参数实际会四舍五入到下一个内存页边界处。sbrk将program break在原有地址上增加delta大小。delta为0表示不改变位置,仅获取program break当前位置。

1
2
3
4
5
6
7
#include <unistd.h>
int brk (
void *__addr
); //成功0 否则-1
void *sbrk (
intptr_t __delta
); //成功返回之前program break 否则(void*)-1

malloc/free

malloc在堆上分配size字节大小的内存,返回指向新分配内存起始位置处的指针,所分配内存未经初始化。malloc基于8或16字节边界来分配内存,参数size为0时页分配一小块应用free释放的内存。free释放ptr指向的内存块。

1
2
3
4
5
6
void *malloc (
size_t __size
); //错误NULL
void free (
void *__ptr
);

Valgrind

安装:

1
sudo apt install valgrind

编译下列示例程序,要求-g选项,开启Debug模式。Valgrind用tool参数指定使用的工具,其中Memcheck用于内存检查,默认选项也是这个。leck-check选项表示客户端程序完成时搜索内存泄露,设为full则每个单独泄露将详细显示。

内存泄露:

1
2
3
4
5
#include <stdlib.h>
int main() {
int* array = (int*)malloc(sizeof(int));
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
valgrind --tool=memcheck --leak-check=full ./test2
==9674== Memcheck, a memory error detector
==9674== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==9674== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==9674== Command: ./test2
==9674==
==9674==
==9674== HEAP SUMMARY:
==9674== in use at exit: 4 bytes in 1 blocks
==9674== total heap usage: 1 allocs, 0 frees, 4 bytes allocated #分配1次 释放0次 共分配4字节
==9674==
==9674== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1 #发生1次泄露
==9674== at 0x4846828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==9674== by 0x10915E: main (main.cpp:4)
==9674==
==9674== LEAK SUMMARY:
==9674== definitely lost: 4 bytes in 1 blocks
==9674== indirectly lost: 0 bytes in 0 blocks
==9674== possibly lost: 0 bytes in 0 blocks
==9674== still reachable: 0 bytes in 0 blocks
==9674== suppressed: 0 bytes in 0 blocks
==9674==
==9674== For lists of detected and suppressed errors, rerun with: -s
==9674== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

数组越界:

1
2
3
4
5
6
7
#include <vector>
#include <iostream>
int main() {
std::vector<int> v(10, 0);
std::cout << v[10] << std::endl;
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
valgrind --tool=memcheck --leak-check=full ./test2
==10459== Memcheck, a memory error detector
==10459== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==10459== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==10459== Command: ./test2
==10459==
==10459== Invalid read of size 4 #越界读4字节
==10459== at 0x1092AD: main (main.cpp:6)
==10459== Address 0x4e290a8 is 0 bytes after a block of size 40 alloc'd
==10459== at 0x4846FA3: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==10459== by 0x1099F3: std::__new_allocator<int>::allocate(unsigned long, void const*) (new_allocator.h:151)
==10459== by 0x1098E0: allocate (alloc_traits.h:482)
==10459== by 0x1098E0: std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) (stl_vector.h:381)
==10459== by 0x109758: std::_Vector_base<int, std::allocator<int> >::_M_create_storage(unsigned long) (stl_vector.h:398)
==10459== by 0x109594: std::_Vector_base<int, std::allocator<int> >::_Vector_base(unsigned long, std::allocator<int> const&) (stl_vector.h:335)
==10459== by 0x1093C0: std::vector<int, std::allocator<int> >::vector(unsigned long, int const&, std::allocator<int> const&) (stl_vector.h:571)
==10459== by 0x10928E: main (main.cpp:5)
==10459==
0
==10459==
==10459== HEAP SUMMARY:
==10459== in use at exit: 0 bytes in 0 blocks
==10459== total heap usage: 3 allocs, 3 frees, 74,792 bytes allocated
==10459==
==10459== All heap blocks were freed -- no leaks are possible
==10459==
==10459== For lists of detected and suppressed errors, rerun with: -s
==10459== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

内存覆盖:

1
2
3
4
5
6
7
8
9
10
11
12
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
char x[50];
int i;
for (i = 0; i < 50; i++)
x[i] = i + 1;
strncpy(x + 20, x, 20);
strncpy(x + 20, x, 21);
return 0;
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
valgrind --tool=memcheck --leak-check=full ./test2
==11249== Memcheck, a memory error detector
==11249== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==11249== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==11249== Command: ./test2
==11249==
==11249== Source and destination overlap in strncpy(0x1ffefffbd9, 0x1ffefffbc5, 21) #源地址和目标地址设置出现重叠
==11249== at 0x484F5A0: strncpy (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==11249== by 0x1091DF: main (main.cpp:10)
==11249==
==11249==
==11249== HEAP SUMMARY:
==11249== in use at exit: 0 bytes in 0 blocks
==11249== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==11249==
==11249== All heap blocks were freed -- no leaks are possible
==11249==
==11249== For lists of detected and suppressed errors, rerun with: -s
==11249== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

内存未初始化:

1
2
3
4
5
6
7
#include <iostream>
int main() {
int x;
if (x == 0)
std::cout << "X is zero" << std::endl;
return 0;
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
valgrind --tool=memcheck --leak-check=full ./test2
==12034== Memcheck, a memory error detector
==12034== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==12034== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==12034== Command: ./test2
==12034==
==12034== Conditional jump or move depends on uninitialised value(s) #访问未初始化内存
==12034== at 0x109179: main (main.cpp:4)
==12034==
==12034==
==12034== HEAP SUMMARY:
==12034== in use at exit: 0 bytes in 0 blocks
==12034== total heap usage: 1 allocs, 1 frees, 73,728 bytes allocated
==12034==
==12034== All heap blocks were freed -- no leaks are possible
==12034==
==12034== Use --track-origins=yes to see where uninitialised values come from
==12034== For lists of detected and suppressed errors, rerun with: -s
==12034== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

内存申请与释放函数不匹配。如果使用malloccallocreallocvallocmemalign分配,则必须使用free释放。如果使用new分配,则必须使用delete释放。如果使用new[]分配,则必须使用delete[]释放。

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>
int main() {
int* p = NULL;
p = (int*)malloc(sizeof(int));
if (p == NULL)
perror("malloc failed");
printf("address [0x%p]\n", p);
delete p;
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
valgrind --tool=memcheck --leak-check=full ./test2
==12820== Memcheck, a memory error detector
==12820== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==12820== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==12820== Command: ./test2
==12820==
address [0x0x4e29080]
==12820== Mismatched free() / delete / delete []
==12820== at 0x484A164: operator delete(void*) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==12820== by 0x10920C: main (main.cpp:9)
==12820== Address 0x4e29080 is 0 bytes inside a block of size 4 alloc'd
==12820== at 0x4846828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==12820== by 0x1091C6: main (main.cpp:5)
==12820==
==12820==
==12820== HEAP SUMMARY:
==12820== in use at exit: 0 bytes in 0 blocks
==12820== total heap usage: 3 allocs, 3 frees, 74,756 bytes allocated
==12820==
==12820== All heap blocks were freed -- no leaks are possible
==12820==
==12820== For lists of detected and suppressed errors, rerun with: -s
==12820== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

calloc/realloc

calloc给一组相同对象分配内存,并初始化为0(malloc就不)。realloc调整一块内存大小。使用这俩函数后的内存应用free释放。

1
2
3
4
5
6
7
8
void *calloc (
size_t __nmemb, //分配对象的数量
size_t __size //每个对象大小
); //成功返回指向这块内存起始处的指针 否则NULL
void *realloc (
void *__ptr, //指向要调整大小的内存块
size_t __size //所需调整大小期望值
); //成功返回指向大小调整后内存块指针 否则NULL

增大已分配内存时,realloc试图合并空闲列表中紧随其后且大小满足要求的内存块。若原内存块位于堆顶,则对堆进行扩展。若位于堆中部,且紧邻其后的空间内存空间大小不足,它会分配一块新内存,将原有数据复制到新内存块中,占用大量CPU资源。

memalign/posix_memalign

memalign分配size个字节的内存,起始地址是alignment整数倍,alignment参数必须为2的整数次幂。posix_memalign功能相同,但alignment参数必须为sizeof(void*)与2的整数次幂的乘积,且已分配内存地址通过memptr参数返回。这俩函数分配的内存块应用free释放。

1
2
3
4
5
6
7
8
9
10
#include <malloc.h>
void *memalign (
size_t __alignment,
size_t __size
); //返回已分配内存的地址
int posix_memalign (
void **__memptr,
size_t __alignment,
size_t __size
);

堆栈上分配

alloca

通过增加栈帧大小从堆栈上分配,size指定分配字节数。不能用freerealloc等。

1
2
3
void *alloca (
size_t __size
); //成功返回指向已分配内存块指针