Как установить ограничения памяти для ваших программ - PullRequest
2 голосов
/ 25 апреля 2020

Как мне установить ограничение на использование ОЗУ, кучи или стека в моей C (или, в принципе, но не в этом случае, C ++) программе? Я использую Visual Studio на Windows 10.

У меня есть полностью работающая программа (ну, библиотека и небольшая программа для запуска базовых c тестов и демонстрации ее для кого-то, кого я обучаю) , и я хочу показать, что происходит, когда распределение памяти не удается. (И я не просто делаю это с тупо-большим выделением, потому что это связанные списки, и я хочу показать сбой выделения памяти в этом контексте.) Итак: как я могу ограничить объем памяти, который разрешено использовать моей программе? и где я это сделаю? Буду ли я что-то делать в ОС, чтобы сказать «это приложение, которое я собираюсь запустить, может использовать только X байтов ОЗУ» (или, может быть, даже сказать, чтобы ограничить размер кучи или стека), было бы что-то, что я бы сделал в Аргументы компилятора, аргументы компоновщика или что?

И код, который я написал, имеет HAS GUARDS, которые предотвращают несанкционированный доступ к памяти и, как следствие, сбой, когда mallo c (или, только в небольшом количестве мест , callo c) возвращает 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.

Итак:

  • Можно ли установить ограничения использования памяти?
  • Если это так, может ли это быть Heap vs Stack-Speci c?
  • Если нет, есть ли способ заставить его использовать только физическую память?
  • Если можно установить ограничения памяти, где я могу это сделать (в ОС, в настройках компилятора, в настройках компоновщика) или даже где-то еще) и как мне это сделать?

И я бы скомпилировал из терминала (а не просто «запустил код», поскольку это проект Visual Studio) следующим образом:

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

Любая помощь или совет о том, где искать, будет высоко ценится! Спасибо! Обновление: люди предложили Job Objects. Это выглядит как С ++ вещь. Будет ли это работать в простой C? (Если нет, то, хотя МОЖЕТ, что этого будет достаточно, это не совсем то, на что я ищу / надеюсь.)

Ответы [ 2 ]

1 голос
/ 25 апреля 2020

Если вы хотите сделать это на уровне пользователя / среды выполнения (и иметь контроль над тестируемым кодом), вы можете реализовать свои собственные safe_malloc(), safe_calloc(), safe_realloc() и safe_free(), которые будет выступать в качестве внешнего интерфейса для своих системных аналогов и будет соответственно увеличивать или уменьшать счетчик numberOfBytesUsed, но потерпит неудачу, если numberOfBytesUsed станет больше фиксированного максимального значения.

(обратите внимание, что сделать это немного сложнее из-за того факта, что free() не содержит аргумента числа свободных байтов, поэтому вы должны «скрыть» эту информацию в выделенных буферах, возвращаемых safe_calloc() и друзьями - обычно путем выделения дополнительных 4 байтов выше того, что запрашивал вызывающий код, размещения значения размера размещения в первых 4 байтах выделения, а затем возврата указателя на первый байт после поля размера выделения)

Если у вас нет контроля над тестируемым кодом (т. е. этот код будет вызывать malloc() и free() напрямую и вы не можете переписать код вместо того, чтобы вызывать ваши функции, вы можете использовать некоторые препроцессоры magi c (например, #define calloc safe_calloc в заголовочном файле, который, как вы знаете, будет включен), чтобы обманным путем заставить протестированный код делать правильные вещи.

Что касается ограничения количества используемого стекового пространства, я не знаю ни одного элегантного способа обеспечить это на уровне кода. Если есть способ применить его с помощью флаг компилятора, вы можете, по крайней мере, заставить программу надежно обработать sh при условии переполнения стека, но это не совсем то же самое, что управляемый / обработанный сбой.

0 голосов
/ 01 мая 2020

В Visual Studio и для демонстрационных целей вы можете использовать сборку _DEBUG и подключиться к управлению памятью CRT с помощью _CrtSetAllocHook. Это позволило бы программе отслеживать распределение памяти и инициировать сбои по своему усмотрению.

...