C - Check currently available free RAM?

2020-02-25 23:37发布

问题:

I know how to use malloc() and free() to allocate memory, but is there also a standard C function to check how much memory is left, so I can call that periodically to make sure my code has no memory leaks?

The only thing I can think of is calling malloc(1) in a endless loop until it returns an error, but shouldn't there be a more efficient way?

回答1:

No, there's no standard C function to do that. There are some platform-specific functions you can use to perform certain types of queries (like working set size), but those probably won't be helpful, because sometimes memory which has been properly free()d is still considered to be allocated by the OS because the malloc implementation might keep the freed memory around in a pool.

If you want to check for memory leaks, I highly recommend using a tool like Valgrind, which runs your program in a virtual machine of sorts and can track memory leaks, among other features.

If you're running on Windows, you can use _CrtDbgReport and/or _CrtSetDbgFlag to check for memory leaks.



回答2:

If in your system malloc() always allocates physical memory, you can call malloc() repeatedly with sizes differing not by 1, but by successive powers of two. That'll be more efficient. Below is an example of how to do it.

If, on the other hand, malloc() only allocates virtual address space without mapping physical memory into it, this won't give you what you want.

Sample code:

#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;
}

Output (ideone):

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


回答3:

If you can afford #ifdef'ing a debug version (possibly in an emulator!), you could just build a debug version of malloc/free that keeps track of the number of bytes currently in use, and "print" it periodically (again - only in the debug version, possibly under an emulator) on whatever output device you have for debugging (a led?), and see if it keeps increasing.

The standard trick is to allocate sizeof(size_t) more than requested, thus storing the size together with the newly allocated memory - but if you're writing a firmware I guess you know it already :)

So... do you have an emulator?

EDIT: I'm so used to computers running at GHz that it didn't occur to me at first, but of course another thing you can do is to just count the number of allocations, not their size -- I can't imagine how this could take too much memory to run.



回答4:

Linux glibc sysconf(_SC_AVPHYS_PAGES) and get_avphys_pages()

These two glibc extensions should give you the available number of pages. We can then just multiply that by the pages size sysconf(_SC_PAGESIZE) to find the total available memory.

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 upstream.

Compile and run:

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

sample output on my 32GiB RAM system:

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 is a bit smaller than 32GiB, and is the total RAM. 0x6383FD000 is the available one.

man get_avphys_pages says it gets its data from /proc/meminfo.

Tested in Ubuntu 19.04.



回答5:

I searched and found this question to help me make an application to animate numerous iterations of fractal functions across multiple arrays of complex values.

Thank you, Alexey Frunze, for your ideone.c code. It has certainly been helpful.

Building upon what I learned, in hopes of more helpfulness, I have written the following:

/* 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");
}

Usage:

count-free-blocks [BLOCK_SIZE]

Input:

> ./count-free-blocks 33554432

Output:

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

I intend to re-purpose these functions into my own application.