【问题标题】:How to set memory caps for your programs如何为您的程序设置内存上限
【发布时间】:2020-08-09 07:53:11
【问题描述】:

如何在我的 C(或者,原则上但在本例中不是 C++)程序中设置 RAM、堆或堆栈的使用上限?我在 Windows 10 上使用 Visual Studio。

我有一个功能齐全的程序(嗯,库和一个运行基本测试并将其演示给我正在辅导的人的小程序),我想展示内存分配失败时会发生什么。 (而且我不只是用一个愚蠢的大分配来做这件事,因为它是链表,我想在那个上下文中显示内存分配失败。)所以:我怎样才能限制我的程序允许使用的内存量,我会在哪里做呢?我会在操作系统中做些什么来告诉它“我要运行的这个应用程序只能使用 X 字节的 RAM”(或者甚至可能告诉它限制堆或堆栈大小),我会在编译器参数、链接器参数还是什么?

我编写的代码有防止非法内存访问和随后崩溃的保护,当 malloc(或仅在少数地方,calloc)返回 NULL 时!所以不要担心非法内存访问和其他事情,我对自己在做什么有一个相当好的想法。

下面是库头 singleLinkList.h 的样子:

#ifndef SINGLELINKEDLIST_H
#define SINGLELINKEDLIST_H

#ifndef KIND_OF_DATA
#define KIND_OF_DATA 3
#endif // !KIND_OF_DATA





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

typedef long long LL_t;


#if KIND_OF_DATA == 1

typedef float data_t;
#define DATA_FORM "%f"

#elif KIND_OF_DATA == 2

typedef double data_t;
#define DATA_FORM "%lf"

#elif KIND_OF_DATA == 3

typedef LL_t data_t;
#define DATA_FORM "%lld"

#else

typedef int data_t;
#define DATA_FORM "%d"

#endif // KIND_OF_DATA == 1, 2, etc...


struct listStruct;


// equivalent to `list_t*` within the .c file
typedef struct listStruct* LS_p;

// equivalent to `const list_t* const` within the .c file
typedef const struct listStruct* const LS_cpc;

typedef struct listStruct* const LS_pc;



int showSizes(void);
size_t queryNodeSize(void);

// returns NULL on failure
LS_p newList(void);

// returns NULL on failure (in memory alloc, at any point), or if given the NULL pointer
LS_p mkListCopy(LS_cpc);

// copies one list into another; leaves the destination unmodified upon failure
//returns a value indicating success/type of failure; returns 0 on success, 
//  various `true` values on failure depending on type
// 1 indicates simple allocation failure
// -1 indicates that you gave the NULL pointer
int copyList(LS_pc dst, LS_cpc src);


//destroys (frees) the given singly-linked list (the list_t* given, and all the list of nodes whose head it holds)
void destroyList(LS_p);

// destroys the list pointed to, then sets it to NULL
//inline void strongDestroyList(LS_p* listP) {
inline void strongDestroyList(struct listStruct** listP) {
    destroyList(*listP);
    *listP = NULL;
}

// Takes a pointer to a list_t
// returns how many elements it has (runs in O(n) time)
//  If you don't understand what `O(n) time` means, go look up "Big O Notation"
size_t len_list(LS_cpc);


//prints a list; returns characters printed
int print_list(LS_cpc);


// gets the data at the specified index of the list; sets the output parameter on failure
data_t indexToData(LS_pc, const size_t ind, int* const err);

// will write the data at ind to the output parameter
//returns a value indicating success/type of failure; returns 0 on success, 
//  various `true` values on failure depending on type
// 1 indicates simple allocation failure
// -1 indicates that you gave the NULL pointer
int copyToPointer(LS_pc, const size_t ind, data_t* const out);


// gets the data at the specified index and removes it from the list; sets output param on failure
data_t popFromInd(LS_pc, const size_t ind, int* const errFlag);

// pops the first item of the list; sets the output param on failure
data_t popFromTop(LS_pc, int* const errFlag);

//returns a value indicating success/type of failure; returns 0 on success, 
//  various `true` values on failure depending on type
// 1 indicates simple allocation failure
// -1 indicates that you gave the NULL pointer
int assignToIndex(LS_pc, const size_t ind, const data_t value);



//returns a value indicating success/type of failure; returns 0 on success, 
//  various `true` values on failure depending on type
// 1 indicates simple allocation failure
// 2 indicates inability to reach the specified index, because it's not that long.
// -1 indicates that you gave the NULL pointer
int insertAfterInd(LS_pc, const size_t ind, const data_t value);


//returns a value indicating success/type of failure; returns 0 on success, 
//  various `true` values on failure depending on type
// 1 indicates simple allocation failure
// -1 indicates that you gave the NULL pointer
int appendToEnd(LS_pc, const data_t value);


//returns a value indicating success/type of failure; returns 0 on success, 
//  various `true` values on failure depending on type
// 1 indicates simple allocation failure
// -1 indicates that you gave the NULL pointer
int insertAtStart(LS_pc list, const data_t value);


#endif // !SINGLELINKEDLIST_H

下面是运行演示/测试的main.c 的样子:

#ifdef __INTEL_COMPILER
#pragma warning disable 1786
#else
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS 1
#endif // _MSC_VER

#endif // __INTEL_COMPILER


#include "singleLinkList.h"

#include <stdio.h>
#include <string.h>



void cleanInputBuffer(void) {
    char c;
    do {
        scanf("%c", &c);
    } while (c != '\n');
}


void fill_avail_memory(void) {
    size_t count = 0;
    LS_p list = NULL;
    size_t length;
    data_t fin;
    int err = 0;
    const size_t nSize = queryNodeSize();
    printf("nSize: %zu\n", nSize);
    int last = -5;
    printf("Do you wish to run the test that involves filling up available memory? "
        "(only 'y' will be interpreted as an affirmative) => ");
    char ans;
    scanf("%c", &ans);
    cleanInputBuffer();
    if ((ans != 'y') && (ans != 'Y')) {
        printf("Okay. Terminating function...\n");
        return;
    }
    printf("Alright! Proceeding...\n");
    list = newList();
    if (list == NULL) {
        printf("Wow, memory allocation failure already. Terminating...\n");
        return;
    }
    print_list(list);
    while (!(last = insertAtStart(list, (data_t)count))) {
        ++count;
    }
    length = len_list(list);
    if (length < 5) {
        print_list(list);
    }
    fin = indexToData(list, 0, &err);
    strongDestroyList(&list);
    printf("Last return value: %d\n", last);
    if (!err) {
        printf("Last inserted value: " DATA_FORM "\n", fin);
    }
    printf("Count, which was incremented on each successfull insert, reached: %zu\n", count);
    printf("Length, which was calculated using len_list, was: %zu\n", length);
}



int main() {
    printf("Hello world!\n");
    showSizes();
    LS_p list = newList();
    print_list(list);

    printf("Printing the list: "); print_list(list);
    printf("Appending 5, inserting 1987 after it...\n");
    appendToEnd(list, 5);
    insertAfterInd(list, 0, 1987);
    printf("Printing the list: "); print_list(list);
    printf("Inserting 15 after index 0...\n");
    insertAfterInd(list, 0, 15);
    printf("Printing the list: "); print_list(list);
    printf("Appending 45 to the list\n");
    appendToEnd(list, 45);
    printf("Printing the list: "); print_list(list);
    //destroyList(list);
    //list = NULL;
    printf("Value of pointer-variable `list` is 0x%p\n", list);
    printf("Destroying list...\n");
    strongDestroyList(&list);
    printf("Value of pointer-variable `list` is 0x%p\n", list);

    printf("\n\n\n");
    fill_avail_memory();

    return 0;
}

__INTEL_COMPILER_MSC_VER 的东西是为了压制关于使用 scanf 的废话。

所以:

  • 是否可以设置内存使用上限?
  • 如果是这样,它可以是堆与堆栈特定的吗?
  • 如果没有,有没有办法让它只使用物理内存?
  • 如果可以设置内存上限,我应该在哪里设置(在操作系统、编译器选项、链接器选项,甚至其他地方)以及如何设置?

我会从终端编译(而不仅仅是“运行代码”,因为它是一个 Visual Studio 项目)如下:

cl singleLinkList.c -c
cl main.c /Zp4 /link singleLinkList.obj

任何帮助,或在哪里寻找建议,将不胜感激!谢谢! 更新:人们建议了工作对象。这看起来是一个 C++ 的东西。它会在纯C中工作吗? (如果没有,那么虽然可能就足够了,但这并不是我想要/希望的。)

【问题讨论】:

  • “如何为您的程序设置内存上限” - 这取决于您运行程序的操作系统。 DOS、QNX、AiX、Solaris、FreeBSD、Linux、Windows、VMS、Z/OS 等,都会有不同的方法来做到这一点。
  • 根据这个answer Job Objects 之类的东西可能对这类任务有用。但我不知道如何使用它们,以及是否可以通过这种方法管理 Windows 10 或任何 64 位应用程序。
  • 据我所知,你不能这样做。在 Linux 上(也可能是 Windows)malloc 设计为永不失败,您的程序将被终止或崩溃。
  • 我不了解 Windows 10,但无论如何,Windows 7 不会为使用我的 MSVC 版本编译的可执行文件分配超过 1.7Gb 的空间。
  • 看起来[如前所述]“工作对象”可能会有所帮助。它说: 系统不断跟踪 PeakProcessMemoryUsed 和 PeakJobMemoryUsed 的值。这使您可以了解每个作业的峰值内存使用量。您可以使用此信息通过 JOB_OBJECT_LIMIT_PROCESS_MEMORY 或 JOB_OBJECT_LIMIT_JOB_MEMORY 值来建立内存限制。

标签: c windows out-of-memory


【解决方案1】:

如果您想在用户/运行时级别执行此操作(并控制正在测试的代码),您可以实现自己的 safe_malloc()safe_calloc()safe_realloc()safe_free()将充当系统提供的对应物的前端,并适当地增加或减少numberOfBytesUsed 计数器,但如果numberOfBytesUsed 将变得大于固定的最大值,则会失败。

(请注意,由于free() 不包含免费字节数参数,因此您必须“隐藏”该信息在分配的safe_calloc() 和朋友返回的缓冲区——通常通过在调用代码请求的内容之上分配额外的 4 个字节,将分配大小值放在分配的前 4 个字节中,然后返回指向第一个字节的指针分配大小字段)

如果您无法控制正在测试的代码(即该代码将直接调用malloc()free(),并且您无法改写代码来调用您的函数,那么您也许可以做一些令人讨厌的预处理魔法(例如,#define calloc safe_calloc 在你知道将被包含的头文件中)来欺骗被测试的代码做正确的事情。

至于限制使用的堆栈空间量,我不知道有任何优雅的方法可以在代码级别强制执行。如果有办法通过编译器标志强制执行它,您至少可以让程序在堆栈溢出条件下可靠地崩溃,但这与受控/处理的故障并不完全相同。

【讨论】:

  • 好的...堆栈部分没问题;我真的不需要限制堆栈的使用。虽然我可以完全或几乎完全控制要测试的内容等(尽管真的,我只关心我的链表库使用了多少堆空间),理想情况下它应该 run out,作为演示内存分配失败的一种方式,因为我想再次证明这种情况发生在链表的上下文中。 (此外,它需要是 8 个字节,而不是 4 个 - 或者取决于 32 位或 64 位模式。)
  • 同意,字节数应该是 size_t 类型。
  • 如果我遵循这种方法(人为返回 NULL),我可以让它使用更多的内存来跟踪所有分配的 void 指针和为每个指针分配的大小,使用另一个链表和搜索它。如果他们分配许多不同的内存块,那会有效吗?一定不行。但它可能工作。但是......仍然不是我想要的。并且不要担心限制堆栈。
  • system provides 您计算字节数的粗略尝试正在尝试完成。但 OP 询问的是限制 RAM,而不是地址空间。
  • @IInspectable 我期待阅读您的回答,详细说明实现提问者目标的更好方法。
【解决方案2】:

对于 Visual Studio,出于演示目的,您可以使用 _DEBUG 构建并通过 _CrtSetAllocHook 挂钩到 CRT 内存管理。这将允许程序监控内存分配并在它认为合适的时候触发故障。

【讨论】:

  • 好的...请详细点?
  • @Poke 该页面链接了一些sample code。将他们的MyAllocHook 修改为return FALSE; 以触发分配失败。例如,您可以基于 _CrtMemCheckpoint 返回的 _CrtMemState::lHighWaterCount
  • ...或者您可以在钩子函数本身中维护运行计数和/或总分配大小,并根据这些决定何时失败。
猜你喜欢
  • 1970-01-01
  • 2014-09-29
  • 2012-08-12
  • 2020-04-30
  • 2015-03-27
  • 1970-01-01
  • 1970-01-01
  • 2016-02-21
相关资源
最近更新 更多