How would I set a cap in RAM, heap, or stack usage in my C (or, in principle but not in this case, C++) program? I am using Visual Studio on Windows 10.
I have a fully-working program (well, library, and a small program to run basic tests and demo it to someone I'm tutoring), and I want to show what happens when memory allocation fails. (And I'm not just doing that with a stupidly-large allocation because it's linked lists, and I want to show memory allocation failure in that context.) So: how can I limit the amount of memory that my program is allowed to use, and where would I do that? Would I do something in the OS to tell it "this application I'm about to run can only use X bytes of RAM" (or maybe even tell it to limit heap or stack size), would there be something I would do in the compiler arguments, the linker arguments, or what?
And the code I've written HAS GUARDS that prevent illegal memory accesses, and subsequently crashing, when malloc (or, in only a small number of places, calloc) returns NULL! So don't worry about illegal memory accesses and stuff, I have a fairly good idea of what I'm doing.
Here's what the library header, singleLinkList.h
, looks like:
#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
And here's what main.c
, which runs the demo/tests, looks like:
#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;
}
(The __INTEL_COMPILER
and _MSC_VER
stuff are to suppress the nonsense about the usage of scanf
.
So:
- Is it possible to set memory usage caps?
- If so, can it be Heap vs Stack-specific?
- If not, is there a way to make it only use physical memory?
- If memory caps can be set, where do I do it (in the OS, in compiler options, in linker options, or even somewhere else) and how do I do it?
And I would compile from the terminal (rather than just 'run code' since it's a Visual Studio project) as follows:
cl singleLinkList.c -c
cl main.c /Zp4 /link singleLinkList.obj
Any help, or advice on where to look, would be much appreciated! Thank you! Update: people have suggested Job Objects. That looks to be a C++ thing. Would it work in plain C? (If not, then while MAYBE it'll suffice, it's not quite what I'm looking/hoping for.)