变量地址的异常大值和负值

Unusually Large values and negative values of addresses of a variable

所以我在学习和练习C语言中指针和变量地址的概念。但是有一件事让我很好奇。我运行的代码是-

    #include <stdio.h>

    int main()
    {
       int *p, n;
       p = &n;
       int *c = NULL;

       printf("Address of variable = %p\n", p);
       printf("Address of variable = %lu\n", p);
       printf("Address of c variable = %lu\n", c);
       return 0;
    }

我确信这段代码打印地址是正确的,我得到的输出是-

    pointer.c: In function ‘main’:
    pointer.c:10:37: warning: format ‘%lu’ expects argument of type ‘long unsigned 
    int’, but argument 2 has type ‘int *’ [-Wformat=]
        10 |     printf("Address of variable = %lu\n", p);
           |                                   ~~^     ~
           |                                     |     |
           |                                     |     int *
           |                                     long unsigned int
           |                                   %ls
    pointer.c:11:39: warning: format ‘%lu’ expects argument of type ‘long unsigned 
    int’, but argument 2 has type ‘int *’ [-Wformat=]
        11 |     printf("Address of c variable = %lu\n", c);
           |                                     ~~^     ~
           |                                       |     |
           |                                       |     int *
           |                                       long unsigned int
           |                                     %ls
   Address of variable = 0x7fffc5a57474
   Address of variable = 140736509342836
   Address of c variable = 0

所以,我想知道这些编译器警告是什么意思,我应该关注这些警告吗?

此外,当我使用 %d 而不是 %p%lu 时,我得到的地址值是一个“负”值,所以负地址可以存在于内存中?

此外,输出中的地址值异常大。它们甚至比我的 16 GB RAM 的大小还要大,我的变量怎么可能存储在一个不存在的位置?

您的代码不正确。

转换说明符 lu 期望其对应参数的类型为 unsigned long,而 d 期望其对应参数的类型为 intpc 的类型为 int *,因此出现警告。是的,这些警告很重要 - 正如您所发现的,至少您会得到乱码输出。

指针类型不是整数类型;它们不必具有与整数类型相同的大小或表示形式(在 x86_64、int 上是 32 位宽,但指针类型是 64 位宽)。为指针类型定义的唯一转换说明符是 p,它期望其对应的参数具有类型 void *.

你的代码应该写得更像

printf( "p = %p\n", (void *) p );
printf( "c = %p\n", (void *) c );

如果您将指针视为数字,则必须小心。在内部,它们通常是地址,通常是数字,但它们是 unsigned 数字。所以,不,您通常不会有“负地址”。

您可能还没有了解计算机如何表示负数。这是常见“two's complement”表示的快速演示,仅使用三位。关键是相同的位模式可以有两种不同的解释,这取决于您是否关心负值:

bit pattern signed int unsigned int
000 0 0
001 1 1
010 2 2
011 3 3
100 -4 4
101 -3 5
110 -2 6
111 -1 7

因此,如您所见,如果您有一个很大的无符号数,但您将其解释为有符号数(例如,通过使用 %d 打印它,您会得到一个负数。

另外两件事要记住:

  1. 指针不一定是整数。它们可能——在 currently-popular x86_64 架构上,它们 ——比整数大。因此,尝试使用 %d 打印它们是双重错误的,并且可能会给你一个完全误导的答案。
  2. 您的程序经常在“地址space”的不同部分使用内存。例如,程序的指令及其全局变量通常分配在内存的低位部分,从地址 0 或附近开始。但是对于通常存储局部变量的“堆栈”来说,开始是很常见的地址 space 顶部的某处,然后向下生长。因此,局部变量的地址通常是一个非常大的数字,看起来比您机器中的内存量还大。但实际上发生的是,两者之间看似巨大的内存量根本没有分配,没有“映射”到您的地址 space,因此不计入物理内存量你的电脑里有。