Is there any reliable way in C to determine if a pointer refers to stack memory vs heap memory? Or is this simply not possible in standard C, and I should always ensure that only heap memory is passed to functions like this?
- C language does not know about the stack or heap. It knows only objects storage durations
- There is no portable way of checking it.
- If you want to stick to one toolchain / linker script / OS you can do this. Here is an expample:
#define _GNU_SOURCE 1 #include <stdio.h> #include <stdbool.h> #include <stdint.h> #include <string.h> #include <pthread.h> #include <unistd.h> // sbrk() // Linker symbols for classic brk-heap extern char end; extern char _end; // ------------------------------------- // /proc/self/maps scanner & classifier // ------------------------------------- typedef enum { PTRMAP_STACK, // [stack] or [stack:<tid>] PTRMAP_HEAP_GLIBC, // [heap] (the main glibc/brk heap mapping) PTRMAP_HEAP_ANON_RW, // anonymous, private, readable+writeable mmap (heap-like arena) PTRMAP_FILE_MAPPED, // file-backed mapping PTRMAP_OTHER, // anything else PTRMAP_NOT_FOUND // not inside any mapping (unlikely if ptr is valid) } ptrmap_class_t; // Trim trailing newline/spaces in-place. static void trimLine(char *s) { if(!s) return; size_t n = strlen(s); while(n && (s[n-1] == '\n' || s[n-1] == '\r' || s[n-1] == ' ' || s[n-1] == '\t')) { s[--n] = '\0'; } } // Classify a pointer by scanning /proc/self/maps once. // Fast path: returns as soon as it finds the covering mapping. static ptrmap_class_t classifyPointerProcMaps(const void *ptr, char *outLine, size_t outLineCap) { if(outLine && outLineCap) { outLine[0] = '\0'; } #if !defined(__linux__) (void)ptr; (void)outLine; (void)outLineCap; return PTRMAP_NOT_FOUND; #else FILE *fp = fopen("/proc/self/maps", "r"); if(!fp) { return PTRMAP_NOT_FOUND; } // Buffer large enough for typical maps lines. char line[1024]; const uintptr_t P = (uintptr_t)ptr; ptrmap_class_t result = PTRMAP_NOT_FOUND; while(fgets(line, sizeof(line), fp)) { // Example line: // 00400000-0040c000 r-xp 00000000 08:02 131073 /bin/cat // 0060c000-0060d000 r--p 0000c000 08:02 131073 /bin/cat // 0060d000-0060e000 rw-p 0000d000 08:02 131073 /bin/cat // 00e2d000-0101e000 rw-p 00000000 00:00 0 [heap] // 7ffcd8b0b000-7ffcd8b2c000 rw-p 00000000 00:00 0 [stack] // // Parse start-end, perms, offset, dev, inode; then get pathname (if any). unsigned long startUL = 0, endUL = 0, offUL = 0; char perms[8] = {0}; unsigned int devMajor = 0, devMinor = 0; unsigned long inode = 0; int nconsumed = 0; // %n stores number of chars consumed so far so we can grab the path. int scanned = sscanf(line, "%lx-%lx %7s %lx %x:%x %lu %n", &startUL, &endUL, perms, &offUL, &devMajor, &devMinor, &inode, &nconsumed); if(scanned < 7) { continue; } const uintptr_t lo = (uintptr_t)startUL; const uintptr_t hi = (uintptr_t)endUL; if(!(P >= lo && P < hi)) { continue; // pointer not in this mapping } // Pointer lies in this mapping: classify it. const bool r = (perms[0] == 'r'); const bool w = (perms[1] == 'w'); const bool x = (perms[2] == 'x'); const bool pflag = (perms[3] == 'p'); // private (copy-on-write) (void)r; (void)x; // r/x not strictly needed for classification // Extract (optional) pathname const char *path = NULL; if(nconsumed > 0 && (size_t)nconsumed < strlen(line)) { // Skip spaces to the pathname token char *p = line + nconsumed; while(*p == ' ' || *p == '\t') { ++p; } trimLine(p); path = (*p ? p : NULL); } // Save the raw line if requested if(outLine && outLineCap) { // Copy original (trimmed) line for diagnostics strncpy(outLine, line, outLineCap - 1); outLine[outLineCap - 1] = '\0'; trimLine(outLine); } // Identify special tags if(path && strncmp(path, "[stack", 6) == 0) // matches "[stack]" and "[stack:tid]" { result = PTRMAP_STACK; } else if(path && strcmp(path, "[heap]") == 0) { result = PTRMAP_HEAP_GLIBC; // the main (brk) heap mapping } else if((inode == 0) && (!path || path[0] == '\0') && pflag && w) { // Anonymous, private, writeable mapping: typical for mmap() arenas. result = PTRMAP_HEAP_ANON_RW; } else if(path && path[0] != '\0' && path[0] != '[') { result = PTRMAP_FILE_MAPPED; } else { result = PTRMAP_OTHER; } break; // done; we found the covering mapping } fclose(fp); return result; #endif } // ------------------------------------- // Classic brk-heap & current-thread stack helpers (from earlier snippet) // ------------------------------------- static bool isPointerOnHeapBrk(const void *ptr) { const uintptr_t heapStart = (uintptr_t)(&_end ? &_end : &end); const uintptr_t heapEnd = (uintptr_t)sbrk(0); if((void*)heapEnd == (void*)-1) { return false; } const uintptr_t p = (uintptr_t)ptr; return (p >= heapStart) && (p < heapEnd); } static bool isPointerOnStack_CurrentThread(const void *ptr) { #if defined(__linux__) pthread_attr_t attr; if(pthread_getattr_np(pthread_self(), &attr) != 0) { return false; } void *stackBase = NULL; size_t stackSize = 0; bool on = false; if(pthread_attr_getstack(&attr, &stackBase, &stackSize) == 0 && stackSize > 0) { const uintptr_t lo = (uintptr_t)stackBase; const uintptr_t hi = lo + stackSize; const uintptr_t p = (uintptr_t)ptr; on = (p >= lo) && (p < hi); } pthread_attr_destroy(&attr); return on; #else (void)ptr; return false; #endif } // ------------------------------------- // Unified classifier // ------------------------------------- typedef enum { PTR_CLASS_STACK, // current thread stack or [stack] mapping PTR_CLASS_HEAP_BRK, // classic brk() heap PTR_CLASS_HEAP_MMAP, // anonymous private RW mapping (heap-like arena) PTR_CLASS_FILE_MAPPED, // file-backed mapping PTR_CLASS_OTHER, // everything else / not found } ptr_class_t; static ptr_class_t classifyPointerExtended(const void *ptr) { // Prefer exact current-thread stack range: if(isPointerOnStack_CurrentThread(ptr)) { return PTR_CLASS_STACK; } // Fall back to maps tags: char line[1024]; const ptrmap_class_t m = classifyPointerProcMaps(ptr, line, sizeof(line)); (void)line; // (keep for debugging if desired) switch(m) { case PTRMAP_STACK: return PTR_CLASS_STACK; case PTRMAP_HEAP_GLIBC: return PTR_CLASS_HEAP_BRK; case PTRMAP_HEAP_ANON_RW: return PTR_CLASS_HEAP_MMAP; case PTRMAP_FILE_MAPPED: return PTR_CLASS_FILE_MAPPED; case PTRMAP_OTHER: case PTRMAP_NOT_FOUND: default: break; } // As a final check, also consider classic brk-heap range if(isPointerOnHeapBrk(ptr)) { return PTR_CLASS_HEAP_BRK; } return PTR_CLASS_OTHER; } #include <stdlib.h> #include <stdio.h> static const char *className(ptr_class_t c) { switch(c) { case PTR_CLASS_STACK: return "STACK"; case PTR_CLASS_HEAP_BRK: return "HEAP_BRK([heap])"; case PTR_CLASS_HEAP_MMAP: return "HEAP_MMAP(anon rw-p)"; case PTR_CLASS_FILE_MAPPED: return "FILE_MAPPED"; default: return "OTHER"; } } int main(void) { int stackVar = 0; void *small = malloc(1024); // often brk-heap void *large = malloc(4 * 1024 * 1024); // often mmap() heap arena printf("&stackVar -> %s\n", className(classifyPointerExtended(&stackVar))); printf("small malloc -> %s\n", className(classifyPointerExtended(small))); printf("large malloc -> %s\n", className(classifyPointerExtended(large))); static int staticVar = 0; printf("&staticVar -> %s\n", className(classifyPointerExtended(&staticVar))); free(small); free(large); return 0; }
https://godbolt.org/z/5E8Tvzcvs
godbolt output:
&stackVar -> STACK small malloc -> HEAP_BRK([heap]) large malloc -> HEAP_MMAP(anon rw-p) &staticVar -> FILE_MAPPED
Of course it cant guarantee that you can realloc of free pointer even if it is referencing heap area
char *c = malloc(1024);and pass youc + 323. Or they could dostruct foo *p = malloc(sizeof *p);and pass you&p->string. Attempting toreallocsuch a pointer would cause problems.dest. If you are going to make a routine that requires a contract with the caller like this, then simply also require the caller to pass an argument indicating whether or not the memory may be reallocated. Or design the routine to always expect reallocatable memory and require the caller to pass that. Or return an error if the destination is too small and let the caller deal with it.