我知道一个地址,但我应该如何判断在哪个部分?使用C的全局区或堆区或堆栈区

I know an address, but how should I judge in what section? Global area or heap area or stack area using C

我有一个C语言编程面试,问题是:知道一个地址,怎么判断在哪一段?全局区或堆区或堆栈区。

正如我在评论中所建议的那样,您可以将所讨论的地址与堆栈或堆上的全局已知对象的地址进行比较。该地址很可能与最近的地址属于同一存储类别。下面是一个演示。有趣的部分是函数 guessStorageLoc().

我已经分配了大的虚拟对象,这样测试对象就不会直接与看起来太容易的参考对象相邻。

编辑:地址差异看起来很可疑 — 我们必须 llabs() 在 64 位系统上使用 long long,否则差异会缩小到 32 位。

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

// This program demonstrates a heuristic to guess 
// whether a given address is global, on the stack 
// or on the heap. 
// Provide any command line argument to get an output of the addresses.
// Tested with gcc 10.2 under msys and VS 2019.

int globalGap1[2000*2000];
int globalRefObj;
int globalGap2[2000*2000];

int globalTestObj;

intptr_t refStack;
enum storageLocE { STACK, GLOBAL, HEAP };
static const char* enumNames[] = { "STACK", "GLOBAL", "HEAP" };

int verbose;

enum storageLocE guessStorageLoc(void* p)
{
    intptr_t ip = (intptr_t)p;

    // ip itself is on the stack and can bserve as reference;-)
    intptr_t refStack= (intptr_t)&ip; 
    intptr_t refHeap = (intptr_t)malloc(sizeof(int));
    intptr_t refGlob = (intptr_t)&globalRefObj;

    size_t heapDiff = llabs(ip - refHeap);
    size_t stackDiff = llabs(ip - refStack);
    size_t globDiff = llabs(ip - refGlob);

    if (verbose)
    {
        printf("Address in question: %p (%"PRIxPTR")\n", p, ip);
        printf("ref stack: %"PRIxPTR", diff: %zu\n", refStack, stackDiff);
        printf("ref heap:  %"PRIxPTR", diff: %zu\n", refHeap, heapDiff);
        printf("ref glob:  %"PRIxPTR", diff: %zu\n", refGlob, globDiff);
    }                                        
    if (heapDiff < stackDiff)
    {
        return heapDiff < globDiff ? HEAP : GLOBAL;
    }
    // ok, closer to stack than heap
    return stackDiff < globDiff ? STACK : GLOBAL;
}

int main(int argc, char **argv)
{
    verbose = argc > 1; // any argument will do ;-)

    int stackTestObj;
    int stackGapDummy[1000];

    int* dynamicTestAddr = malloc(sizeof(int));
    malloc(1000 * 1000); // gap

    printf("\nstack object: guess is %s\n\n", enumNames[guessStorageLoc(&stackTestObj)]);
    printf("\nheap object: guess is %s\n\n", enumNames[guessStorageLoc(dynamicTestAddr)]);
    printf("\nglobal object: guess is %s\n\n", enumNames[guessStorageLoc(&globalTestObj)]);
}

示例会话:

$ gcc -Wall -Wno-unused-variable -Wno-unused-result -o GuessStorageCategory GuessStorageCategory.c && ./GuessStorageCategory xxx
Address in question: 0xffffcc24 (ffffcc24)
ref stack: ffffbc18, diff: 4108
ref heap:  800000450, diff: 30064785452
ref glob:  101349400, diff: 20236252

stack object: guess is STACK

Address in question: 0x800000430 (800000430)
ref stack: ffffbc18, diff: 30064789528
ref heap:  8000004c0, diff: 144
ref glob:  101349400, diff: 30044549168

heap object: guess is HEAP

Address in question: 0x10228b820 (10228b820)
ref stack: ffffbc18, diff: 36240392
ref heap:  8000004e0, diff: 30028549312
ref glob:  101349400, diff: 16000032

global object: guess is GLOBAL

但是等等!如果我们应用优化,我们会得到错误的结果:

$ gcc -O3 -Wall -Wno-unused-variable -Wno-unused-result -o GuessStorageCategory GuessStorageCategory.c && ./GuessStorageCategory xxx
Address in question: 0xffffcc1c (ffffcc1c)
ref stack: ffffcb98, diff: 132
ref heap:  800000450, diff: 30064785460
ref glob:  101349420, diff: 20236292

stack object: guess is STACK

Address in question: 0x800000430 (800000430)
ref stack: ffffcb98, diff: 30064785560
ref heap:  8000004c0, diff: 144
ref glob:  101349420, diff: 30044549136

heap object: guess is HEAP

Address in question: 0x100407010 (100407010)
ref stack: ffffcb98, diff: 4236408
ref heap:  8000004e0, diff: 30060549328
ref glob:  101349420, diff: 16000016

global object: guess is STACK

原因可能是全局对象和堆栈对象一开始就靠得很近;编译器现在消除了未使用的堆栈“间隙”分配,这些分配将堆栈引用对象从全局内存中移开。但是编译器无法消除分隔全局引用和测试对象的未使用的 global 间隙变量;它们是导出符号,被编译器认为是一种可观察到的行为。

如果我们将它们设为静态文件,这样它们就不再是程序外部可见的符号,编译器可以自由地消除它们,将引用和测试变量放在相邻的存储中,从而产生预期的结果。

这当然是一个警示事件:我们实际上只是在进行试探。不幸的分配模式可能会使这种启发式失败。