C语言 - 检查当前可用的空闲RAM?

12

我知道如何使用malloc()free()来分配内存,但是否也有一种标准的C函数可以检查剩余多少内存,以便我可以定期调用它来确保我的代码没有内存泄漏?

我唯一能想到的是在一个无限循环中调用malloc(1)直到返回错误,但这难道不应该有更高效的方法吗?


1
为什么不直接在程序上使用valgrind来检查内存泄漏呢? - Mike
2
请注意,在无限循环中调用malloc函数可能永远不会失败,因为大多数系统只在“第一次接触”时分配内存。 - Ben
1
相关:https://dev59.com/HnE85IYBdhLWcg3w_I38 - Faruk Sahin
1
@Ben:每次调用malloc都会保留一部分虚拟地址空间,因此最终会返回错误。 - Fred Foo
1
@larsmans,确实,malloc也需要(物理)内存来跟踪分配的块(即使您放弃指针)。因此,您不能永远malloc。但是,这对于知道剩余多少内存毫无帮助。 - Ben
显示剩余3条评论
5个回答

8

Linux glibc sysconf(_SC_AVPHYS_PAGES)get_avphys_pages()

这两个glibc扩展可以为您提供可用页面的数量。然后,我们只需将其乘以页面大小sysconf(_SC_PAGESIZE),就可以找到总可用内存大小(以字节为单位)。

main.c

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/sysinfo.h>
#include <unistd.h>

int main(void) {
    /* PAGESIZE is POSIX: http://pubs.opengroup.org/onlinepubs/9699919799/
     * but PHYS_PAGES and AVPHYS_PAGES are glibc extensions. I bet those are
     * parsed from /proc/meminfo. */
    printf(
        "sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGESIZE) = 0x%lX\n",
        sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGESIZE)
    );
    printf(
        "sysconf(_SC_AVPHYS_PAGES) * sysconf(_SC_PAGESIZE) = 0x%lX\n",
        sysconf(_SC_AVPHYS_PAGES) * sysconf(_SC_PAGESIZE)
    );

    /* glibc extensions. man says they are parsed from /proc/meminfo. */
    printf(
        "get_phys_pages() * sysconf(_SC_PAGESIZE) = 0x%lX\n",
        get_phys_pages() * sysconf(_SC_PAGESIZE)
    );
    printf(
        "get_avphys_pages() * sysconf(_SC_PAGESIZE) = 0x%lX\n",
        get_avphys_pages() * sysconf(_SC_PAGESIZE)
    );
}

GitHub主页.

编译和运行:

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out

我在我的32GiB RAM系统上获得了以下示例输出:

sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGESIZE) = 0x7CCFFC000
sysconf(_SC_AVPHYS_PAGES) * sysconf(_SC_PAGESIZE) = 0x6383FD000
get_phys_pages() * sysconf(_SC_PAGESIZE) = 0x7CCFFC000
get_avphys_pages() * sysconf(_SC_PAGESIZE) = 0x6383FD000

0x7CCFFC000 是总内存大小,略小于32GiB。0x6383FD000 是可用内存大小。

man get_avphys_pages 表示该命令从 /proc/meminfo 中获取数据。

在 Ubuntu 19.04 中进行测试。


7
没有标准的C函数可以做到这一点。有一些平台特定的函数可以用来执行某些类型的查询(如工作集大小),但这些可能不会有帮助,因为有时已经正确释放的内存仍然被操作系统认为是已分配的,因为malloc实现可能会在池中保留已释放的内存。
如果你想检查内存泄漏,我强烈推荐使用像Valgrind这样的工具,在虚拟机中运行程序,可以跟踪内存泄漏等特性。
如果你正在运行Windows,可以使用_CrtDbgReport和/或_CrtSetDbgFlag来检查内存泄漏。

该程序是在Cortex M0 CPU上运行的固件,并使用了许多ARM特定的调用/指令,因此我认为它很难被Valgrind分析,并且设备上几乎没有足够的内存来运行我的代码,更不要说添加虚拟机了。 - Maestro
2
在这种情况下,测量您在执行开始时拥有多少内存,并编写包装器函数以围绕mallocfree,从您的起始点递减和递增,正如loreb在另一个答案中建议的那样。 - Alex Reynolds

4

如果你的系统中malloc()总是分配物理内存,你可以连续调用malloc()来分配大小不仅相差1,而是连续2的幂次方。这样会更有效率。以下是如何做到的示例。

另一方面,如果malloc()只分配虚拟地址空间而不映射物理内存,则无法得到预期结果。

示例代码:

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

void* AllocateLargestFreeBlock(size_t* Size)
{
  size_t s0, s1;
  void* p;

  s0 = ~(size_t)0 ^ (~(size_t)0 >> 1);

  while (s0 && (p = malloc(s0)) == NULL)
    s0 >>= 1;

  if (p)
    free(p);

  s1 = s0 >> 1;

  while (s1)
  {
    if ((p = malloc(s0 + s1)) != NULL)
    {
      s0 += s1;
      free(p);
    }
    s1 >>= 1;
  }

  while (s0 && (p = malloc(s0)) == NULL)
    s0 ^= s0 & -s0;

  *Size = s0;
  return p;
}

size_t GetFreeSize(void)
{
  size_t total = 0;
  void* pFirst = NULL;
  void* pLast = NULL;

  for (;;)
  {
    size_t largest;
    void* p = AllocateLargestFreeBlock(&largest);

    if (largest < sizeof(void*))
    {
      if (p != NULL)
        free(p);
      break;
    }

    *(void**)p = NULL;

    total += largest;

    if (pFirst == NULL)
      pFirst = p;

    if (pLast != NULL)
      *(void**)pLast = p;

    pLast = p;
  }

  while (pFirst != NULL)
  {
    void* p = *(void**)pFirst;
    free(pFirst);
    pFirst = p;
  }

  return total;
}

int main(void)
{
  printf("Total free: %zu\n", GetFreeSize());
  printf("Total free: %zu\n", GetFreeSize());
  printf("Total free: %zu\n", GetFreeSize());
  printf("Total free: %zu\n", GetFreeSize());
  printf("Total free: %zu\n", GetFreeSize());
  return 0;
}

输出 (ideone):

Total free: 266677120
Total free: 266673024
Total free: 266673024
Total free: 266673024
Total free: 266673024

这并没有考虑到某些实现为每个malloc分配的缓冲区分配额外字节(用于对齐或验证缓冲区溢出)。 - AlfredD
@AlfredD malloc() 无论如何也不会让你拥有那些字节。 - Alexey Frunze

2
如果你有条件使用#ifdef来调试版本(可能在模拟器中),那么你可以构建一个跟踪当前使用的字节数的malloc/free调试版本,并定期地“打印”它(仅在调试版本下,可能在模拟器下)到用于调试的任何输出设备上(例如LED),并查看它是否不断增加。
标准技巧是比请求的内存大小多分配sizeof(size_t)个字节,从而将大小与新分配的内存一起存储 - 但如果你正在编写固件,我想你已经知道了这一点 :)
所以...你有模拟器吗?
编辑:我习惯了计算机运行在GHz,一开始没有想到,但当然你还可以做的另一件事情是只计算分配的数量,而不是它们的大小 - 我无法想象这需要多少内存才能运行。

1

我搜索并找到了这个问题,以帮助我制作一个应用程序,在多个复杂值数组中动画多次迭代的分形函数。

谢谢Alexey Frunze提供的ideone.c代码。它确实很有帮助。

在此基础上,为了更加有益,我编写了以下内容:

/* File: count-free-blocks.c
 *
 * Revision: 2018-24-12
 * 
 * Copyright (C) Randall Sawyer
 * <https://stackoverflow.com/users/341214/randall-sawyer>
 */

#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

size_t  available_blocks (size_t      block_sz);
size_t  largest_n_blocks (size_t      block_sz);
size_t  try_alloc        (size_t      n_blocks,
                          size_t      block_sz);
void    report           (int         indent,
                          const char *format,
                          ...);

int main (int argc, const char **argv)
{
  size_t  n_blocks,
          block_sz = 0;

  if (argc > 1 && sscanf (argv[1], "%zu", &block_sz) != 1)
    report (0, "Argument `%s' is not a valid block size.", argv[1]);

  if (block_sz == 0)
  {
    report (0, "Using 1 byte block size...");
    block_sz = 1;
  }

  n_blocks = available_blocks (block_sz);

  report (0, "Available memory: %zu blocks of %zu bytes == %zu bytes",
          n_blocks, block_sz, n_blocks * block_sz);

  return 0;
}

size_t
available_blocks (size_t block_sz)
{
  size_t  n_blocks[2];

  report (0, "calculating free memory...");

  /* Calculate maximum number of blocks of given size which can be
   * repeatedly allocated.
   */
  do {

    for ( n_blocks[0] = largest_n_blocks (block_sz);
         (n_blocks[1] = largest_n_blocks (block_sz)) < n_blocks[0];
          n_blocks[0] = n_blocks[1] );

    report (1, "check once more...");

  } while (try_alloc (n_blocks[0], block_sz) != n_blocks[0]);

  return n_blocks[0];
}

size_t
largest_n_blocks (size_t block_sz)
{
  static
  const char *f  = "phase %d";
  size_t      n_blocks, max, bit;

  report (1, "calculating largest number of free blocks...");

  /* Phase 1:
   * 
   * Find greatest allocatable number-of-blocks such that
   * it has only one bit set at '1' and '0' for the rest.
   */
  report (2, f, 1);

  n_blocks = ~(UINTPTR_MAX >> 1);     // only MSB is set
  max      = UINTPTR_MAX / block_sz;  // maximimum number of blocks

  while (n_blocks && !(n_blocks & max))
    n_blocks >>= 1;

  while (try_alloc (n_blocks, block_sz) != n_blocks)
    n_blocks >>= 1;

  /* Phase 2:
   * 
   * Starting with first allocatable number-of-blocks, add decreasingly
   * significant bits to this value for each successful allocation.
   */
  report (2, f, 2);

  for ( bit = n_blocks >> 1; bit; bit >>= 1)
  {
    size_t  n = n_blocks | bit;

    if (try_alloc (n, block_sz) == n)
      n_blocks = n;
  }

  /* Phase 3:
   * For as long as allocation cannot be repeated,
   * decrease number of blocks.
   */
  report (2, f, 3);

  while (try_alloc (n_blocks, block_sz) != n_blocks)
    --n_blocks;

  report (1, "free blocks: %zu", n_blocks);

  return n_blocks;
}

size_t
try_alloc  (size_t  n_blocks,
            size_t  block_sz)
{
  if (n_blocks != 0)
  {
    /* Try to allocate all of the requested blocks.
     * If successful, return number of requested blocks;
     * otherwise, return 0.
     */
    void *p = calloc (n_blocks, block_sz);

    report (3, "try %zu blocks of %zu bytes: %s",
            n_blocks, block_sz, p ? "success" : "failure");

    if (p)
    {
      free (p);
      return n_blocks;
    }
  }

  return 0;
}

#define MAX_INDENT    8
#define INDENT_SPACES "        "

void
report (int         indent,
        const char *format,
        ...)
{
  const char  padding[MAX_INDENT+1] = INDENT_SPACES;
  va_list     args;

  if (indent > MAX_INDENT)
    indent = MAX_INDENT;

  if (indent > 0)
    printf ("%s", &padding[8-indent]);

  va_start (args, format);
  vprintf (format, args);
  va_end (args);

  printf ("\n");
}

用法:

count-free-blocks [BLOCK_SIZE]

输入:

> ./count-free-blocks 33554432

输出:

calculating free memory...
 calculating largest number of free blocks...
  phase 1
   try 64 blocks of 33554432 bytes: success
  phase 2
   try 96 blocks of 33554432 bytes: failure
   try 80 blocks of 33554432 bytes: success
   try 88 blocks of 33554432 bytes: success
   try 92 blocks of 33554432 bytes: failure
   try 90 blocks of 33554432 bytes: success
   try 91 blocks of 33554432 bytes: success
  phase 3
   try 91 blocks of 33554432 bytes: success
 free blocks: 91
 calculating largest number of free blocks...
  phase 1
   try 64 blocks of 33554432 bytes: success
  phase 2
   try 96 blocks of 33554432 bytes: failure
   try 80 blocks of 33554432 bytes: success
   try 88 blocks of 33554432 bytes: success
   try 92 blocks of 33554432 bytes: failure
   try 90 blocks of 33554432 bytes: success
   try 91 blocks of 33554432 bytes: success
  phase 3
   try 91 blocks of 33554432 bytes: success
 free blocks: 91
 check once more...
   try 91 blocks of 33554432 bytes: success
Available memory: 91 blocks of 33554432 bytes == 3053453312 bytes

我打算将这些函数重新用于我的应用程序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接