C语言基础——内存操作函数

malloc

#include <stdlib.h>
void *malloc(size_t size);

作用:分配一块内存空间。
参数:以字节为单位的内存大小。
返回值:成功返回指向该内存首地址的指针(对齐后),对齐标准取决于32位编译还是64位编译,32位下返回的地址总是8的倍数,64位下返回的地址总是16的倍数。失败返回NULL。

calloc

#include <stdlib.h>
void *calloc(size_t nitems, size_t size);

作用:与malloc的不同之处是calloc会将分配的内存初始化为零。
参数:
---- nitems:要分配的元素个数。
---- size:要分配的单个元素的大小。
返回值:成功返回指向该内存首地址的指针。失败返回NULL。

realloc

#include <stdlib.h>
void *realloc(char *ptr, size_t size);

作用:尝试重新分配之前malloc或者realloc分配的内存的大小。
参数:
---- ptr:指向需要重新分配的内存块的指针。如果ptr指向NULL则会分配一块相应大小的新的内存。
---- size:以字节为单位的新的内存块的大小。如果是0,且ptr有效,则会释放ptr指向的内存,返回NULL。
返回值:成功返回新的指针。失败返回NULL。

free

#include <stdlib.h>
void free(void *ptr);

作用:释放调用malloc、calloc和realloc所分配的空间。
参数:指向内存块的指针。如果是NULL则忽略。
C语言中的malloc、calloc、realloc和free函数实际上是对下面的brk()和mmap()系统调用的封装。

brk和sbrk

系统调用函数会发生从用户态到内核态的切换,这类上下文切换比较消耗资源。因此尽量避免直接使用系统调用函数,而是用相应功能的C语言函数来代替。
image
一个linux进程的虚拟内存划分如上图所示。堆区的内存从低地址到高地址增加,可以通过移动brk的位置来分配或者释放内存。当然实际运行中的Linux系统内存分配并没有这样简单,详细内容可以参考这篇笔记:https://github.com/luckilzy/blog/issues/7

#include <unistd.h>
int brk(void *addr);
void *sbrk(intptr_t increment);

作用:brk()函数通过更改brk指针来操作堆内存,sbrk()函数通过设置brk指针的偏移量来操作堆内存。
参数:
---- addr:直接将brk指针更改到指向addr地址处。
---- increment:将brk指针偏移increment个单位。
返回值:
---- brk():成功返回0。失败返回-1并设置错误号。
---- sbrk():成功返回brk之前的位置,即分配内存的起始地址。失败返回(void *)-1并设置错误号。

示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define USIZE 1024

int main(void)
{
      printf("pid: %d\n", getpid());

      /* 内存页的大小 */
      long PageSize = sysconf(_SC_PAGE_SIZE);
      printf("Virtrual Memory Page: %ld\n", PageSize);

      void *TmpPointer = NULL;
      void *CurrPointer = NULL;
      TmpPointer = CurrPointer = sbrk(0);
      printf("program break position1: %p\n", CurrPointer);

      getchar();

      /* 分配一个页大小的内存 */
      TmpPointer = sbrk(PageSize);
      if (TmpPointer == (void *)-1)
      {
        perror("sbrk");
        exit(-1);
      }
      CurrPointer = sbrk(0);
      printf("program break position2: %p\n",CurrPointer);

      getchar();

      /* 释放内存 */
      TmpPointer = sbrk(-1 * PageSize);
      if (TmpPointer== (void *)-1)
      {
        perror("sbrk");
        exit(-1);
      }

      CurrPointer = sbrk(0);
      printf("program break position3: %p\n", CurrPointer);

      getchar();

      return 0;
}

开始的brk指向堆顶的0x561c5f2b6000地址处:

[luckilzy@:~/work/my_test]$./sbrk
pid: 50511
Virtrual Memory Page: 4096
program break position1: 0x561c5f2b6000
...

[luckilzy@:~/test]$cat /proc/50511/maps
561c5dd9f000-561c5dda0000 r-xp 00000000 08:10 157949411                  /luckilzy/work/my_test/sbrk
561c5df9f000-561c5dfa0000 r--p 00000000 08:10 157949411                  /luckilzy/work/my_test/sbrk
561c5dfa0000-561c5dfa1000 rw-p 00001000 08:10 157949411                  /luckilzy/work/my_test/sbrk
561c5f295000-561c5f2b6000 rw-p 00000000 00:00 0                          [heap]
...

当分配了4096B的内存后,brk指向新的堆顶0x561c5f2b7000处:

[luckilzy@:~/work/my_test]$./sbrk
pid: 50511
Virtrual Memory Page: 4096
program break position1: 0x561c5f2b6000

program break position2: 0x561c5f2b7000
...

[luckilzy@:~/test]$cat /proc/50511/maps
561c5dd9f000-561c5dda0000 r-xp 00000000 08:10 157949411                  /luckilzy/work/my_test/sbrk
561c5df9f000-561c5dfa0000 r--p 00000000 08:10 157949411                  /luckilzy/work/my_test/sbrk
561c5dfa0000-561c5dfa1000 rw-p 00001000 08:10 157949411                  /luckilzy/work/my_test/sbrk
561c5f295000-561c5f2b7000 rw-p 00000000 00:00 0                          [heap]
...

当释放了堆顶的4096B的内存后,brk又指回新的堆顶0x561c5f2b6000处:

[luckilzy@:~/work/my_test]$./sbrk
pid: 50511
Virtrual Memory Page: 4096
program break position1: 0x561c5f2b6000

program break position2: 0x561c5f2b7000

program break position3: 0x561c5f2b6000
...

[luckilzy@:~/test]$cat /proc/50511/maps
561c5dd9f000-561c5dda0000 r-xp 00000000 08:10 157949411                  /luckilzy/work/my_test/sbrk
561c5df9f000-561c5dfa0000 r--p 00000000 08:10 157949411                  /luckilzy/work/my_test/sbrk
561c5dfa0000-561c5dfa1000 rw-p 00001000 08:10 157949411                  /luckilzy/work/my_test/sbrk
561c5f295000-561c5f2b6000 rw-p 00000000 00:00 0                          [heap]
...

maps中的字段含义:
---- 0x561c5f295000-0x561c5f2b6000:堆段的地址范围。
---- rw-p:分配的这块内存可读可写不可执行,是私有的。
---- 00000000:是文件偏移量,因为它没有映射到任何文件,所以是0。
---- 00:00:是 主:从 设备号,因为它没有映射到任何设备,所以都是0。
---- 0:是索引号,因为它没有映射到任何文件,所以是0。
--- [heap]:堆段。

mmap与munmap

内存映射就是将磁盘上的一块内容和虚拟内存相关联。

#include <unistd.h>
#include <sys/mman.h>
void *mmap(void *start, size_t length, int port, int flags, int fd, off_t offset);

作用:再调用进程的虚拟内存空间内创建一个到磁盘的映射。
参数:
---- start:映射虚拟内存的首地址。一般为NULL,由内核自动分配。
---- length:映射虚拟内存的长度,要大于零。
---- port:映射的虚拟内存的权限。不能和文件描述符fd的权限冲突,要小于等于fd的权限。
-------- PROT_EXEC:执行权限。
-------- PROT_READ:读权限。
-------- PROT_WRITE:写权限。
-------- PROT_NONE:不可访问。
---- flags:对映射区内存数据的修改是否更新到磁盘以及是否对映射到磁盘同一区域的其他进程可见。
-------- MAP_SHARED:共享内存映射。对映射区内存数据的修改将会更新到磁盘以及对映射到磁盘同一区域的其他进程可见。
-------- MAP_PRIVATE:创建一个私有的,写时复制的映射。
-------- MAP_ANONIMOUS(MAP_ANON):匿名映射,不需要指定磁盘文件,映射后的内存初始化为零。要求fd是-1且offset是0。
---- fd:映射文件的文件描述符。
---- offset:磁盘中文件开始映射位置的偏移量。必须是页面大小(一般为4KB)的整数倍。
返回值:成功返回一个指向映射内存首地址的指针。失败返回(void *)-1,并设置错误号。

#include <unistd.h>
#include <sys/mman.h>
int munmap(void *addr, size_t size);

作用:删除从虚拟地址addr开始的,size长度的虚拟映射区域。
返回值:成功返回0。失败返回-1,并设置错误号。

示例

使用匿名映射来分配100MB的内存。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

#define USIZE 1024

int main(void)
{
      printf("pid: %d\n", getpid());

      /* 内存页的大小 */
      long PageSize = sysconf(_SC_PAGE_SIZE);
      printf("Virtrual Memory Page: %ld\n", PageSize);
      printf("before mmap\n");

      getchar();

      /* 分配一个页大小的内存 */
      void *retptr = mmap(NULL, 100 * USIZE * USIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
      if (retptr == (void *)-1)
      {
        perror("mmap");
        exit(-1);
      }
      printf("after mmap\n");

      getchar();

      /* 释放内存 */
      int ret = munmap(retptr, 100 * USIZE * USIZE);
      if (ret == -1)
      {
        perror("munmap");
        exit(-1);
      }
      printf("after munmap\n");

      getchar();

      return 0;
}

开始时的堆结构:

[luckilzy@:~/work/my_test]$./mmap
pid: 18737
Virtrual Memory Page: 4096
before mmap
...

[luckilzy@:~/test]$cat /proc/18737/maps
564ee190a000-564ee190b000 r-xp 00000000 08:10 157949418                  /luckilzy/work/my_test/mmap
564ee1b0a000-564ee1b0b000 r--p 00000000 08:10 157949418                  /luckilzy/work/my_test/mmap
564ee1b0b000-564ee1b0c000 rw-p 00001000 08:10 157949418                  /luckilzy/work/my_test/mmap
564ee26a9000-564ee26ca000 rw-p 00000000 00:00 0                          [heap]
...

mmap()函数映射了100MB的内存:

[luckilzy@:~/work/my_test]$./mmap
pid: 18737
Virtrual Memory Page: 4096
before mmap

after mmap
...

[luckilzy@:~/test]$cat /proc/18737/maps
564ee190a000-564ee190b000 r-xp 00000000 08:10 157949418                  /luckilzy/my_test/mmap
564ee1b0a000-564ee1b0b000 r--p 00000000 08:10 157949418                  /luckilzy/my_test/mmap
564ee1b0b000-564ee1b0c000 rw-p 00001000 08:10 157949418                  /luckilzy/work/my_test/mmap
564ee26a9000-564ee26ca000 rw-p 00000000 00:00 0                          [heap]
7f7de7284000-7f7ded684000 rw-p 00000000 00:00 0
...

mummap()函数释放了100MB的内存:

[luckilzy@:~/work/my_test]$./mmap
pid: 18737
Virtrual Memory Page: 4096
before mmap

after mmap

after munmap
...

[luckilzy@:~/test]$cat /proc/18737/maps
564ee190a000-564ee190b000 r-xp 00000000 08:10 157949418                  /luckilzy/work/my_test/mmap
564ee1b0a000-564ee1b0b000 r--p 00000000 08:10 157949418                  /luckilzy/work/my_test/mmap
564ee1b0b000-564ee1b0c000 rw-p 00001000 08:10 157949418                  /luckilzy/work/my_test/mmap
564ee26a9000-564ee26ca000 rw-p 00000000 00:00 0                          [heap]
...

参考资料

  1. https://sploitfun.wordpress.com/2015/02/11/syscalls-used-by-malloc/
posted @ 2025-04-22 09:20  luckilzy  阅读(28)  评论(0)    收藏  举报