首页 文章

如果不是内存地址,究竟什么是C指针?

提问于
浏览
201

在有关C的信誉良好的来源中,在讨论 & 运算符后给出以下信息:

...有点不幸的是,术语[地址]仍然存在,因为它使那些不知道地址的人感到困惑,并误导那些做的人:思考指针就好像他们是地址通常会导致悲伤 . ..

我读过的其他材料(来自同等声誉的来源,我会说)总是毫不掩饰地将指针和 & 运算符称为给出内存地址 . I would love to keep searching for the actuality of the matter, but it is kind of difficult when reputable sources KIND OF disagree.

现在我有点困惑 - 究竟什么是指针,然后,如果不是内存地址?

P.S.

作者后来说:......我将继续使用术语'address of',因为发明一个不同的[术语]会更糟 .

25 回答

  • 0

    C标准没有定义指针在内部的内容以及它在内部的工作方式 . 这是故意的,以便不限制平台的数量,其中C可以实现为编译或解释语言 .

    指针值可以是某种ID或句柄,也可以是多个ID的组合(对x86段和偏移有问题),不一定是实际的内存地址 . 此ID可以是任何内容,甚至是固定大小的文本字符串 . 非地址表示对于C解释器可能特别有用 .

  • 1

    Pointer vs Variable

    在这幅图片中,

    pointer_p是一个位于0x12345的指针,指向0x34567处的变量variable_v .

  • 3

    它说"because it confuses those who don't know what addresses are about" - 而且,'s true: if you learn what addresses are about, you'将不会混淆 . 从理论上讲,指针是一个指向另一个的变量,实际上是一个地址,它指向的变量的地址 . 我不知道为什么要隐瞒这个事实,它更接近了解计算机是如何工作的 . 前进!

  • 62

    指针是HOLDS内存地址的变量,而不是地址本身 . 但是,您可以取消引用指针 - 并访问内存位置 .

    例如:

    int q = 10; /*say q is at address 0x10203040*/
    int *p = &q; /*means let p contain the address of q, which is 0x10203040*/
    *p = 20; /*set whatever is at the address pointed by "p" as 20*/
    

    而已 . 就这么简单 .

    enter image description here

    一个演示我正在说什么的程序及其输出在这里:

    http://ideone.com/rcSUsb

    该程序:

    #include <stdio.h>
    
    int main(int argc, char *argv[])
    {
      /* POINTER AS AN ADDRESS */
      int q = 10;
      int *p = &q;
    
      printf("address of q is %p\n", (void *)&q);
      printf("p contains %p\n", (void *)p);
    
      p = NULL;
      printf("NULL p now contains %p\n", (void *)p);
      return 0;
    }
    
  • 18

    你可以这样看 . 指针是表示可寻址存储空间中的地址的值 .

  • 8

    C指针非常类似于内存地址,但是抽象了与机器相关的细节,以及在较低级别指令集中找不到的一些功能 .

    例如,C指针的输入相对丰富 . 如果通过结构数组递增指针,它可以很好地从一个结构跳转到另一个结构 .

    指针受转换规则的约束,并提供编译时类型检查 .

    有一个特殊的“空指针”值,它在源代码级别是可移植的,但其表示可能不同 . 如果为指针指定一个值为零的整型常量,则该指针将采用空指针值 . 如果你用这种方式初始化一个指针也是同样的 .

    指针可以用作布尔变量:如果它不是null,则测试为true,如果为null,则测试为false .

    在机器语言中,如果空指针是一个有趣的地址,如0xFFFFFFFF,那么您可能必须对该值进行显式测试 . C隐藏了你 . 即使空指针是0xFFFFFFFF,您也可以使用 if (ptr != 0) { /* not null! */} 进行测试 .

    破坏类型系统的指针的使用导致未定义的行为,而机器语言中的类似代码可能被很好地定义 . 汇编程序将汇编您编写的指令,但C编译器将根据您没有做错的假设进行优化 . 如果 float *p 指针指向 long n 变量,并且执行了 *p = 0.0 ,则编译器不需要处理它 . 随后使用 n 将不必读取浮点值的位模式,但也许,它将是一个优化的访问,它基于"strict aliasing"假设没有触及 n !也就是说,假设程序运行良好,因此 p 不应该指向 n .

    在C中,指向代码和指向数据的指针是不同的,但在许多体系结构中,地址是相同的 . 可以开发具有“胖”指针的C编译器,即使目标体系结构没有 . 胖指针意味着指针不仅仅是机器地址,还包含其他信息,例如有关所指向对象大小的信息,用于边界检查 . 便携式编写的程序很容易移植到这样的编译器 .

    所以你可以看到,机器地址和C指针之间存在许多语义差异 .

  • 16

    简单地说,指针实际上是分段机制的一部分,它在分段后转换为线性地址,然后在分页后转换为物理地址 . 物理地址实际上是从你的ram处理的 .

    Selector  +--------------+         +-----------+
          ---------->|              |         |           |
                     | Segmentation | ------->|  Paging   |
            Offset   |  Mechanism   |         | Mechanism |
          ---------->|              |         |           |
                     +--------------+         +-----------+
            Virtual                   Linear                Physical
    
  • 12

    C或C指针与简单存储器地址不同的另一种方式对于我在其他答案中没有看到的不同指针类型(尽管考虑到它们的总大小,我可能忽略了它) . 但它可能是最重要的一个,因为即使是经验丰富的C / C程序员也可以绊倒它:

    编译器可能会假设不兼容类型的指针即使它们明确指出也不指向相同的地址,这可能会给出使用简单指针==地址模型无法实现的行为 . 考虑以下代码(假设 sizeof(int) = 2*sizeof(short) ):

    unsigned int i = 0;
    unsigned short* p = (unsigned short*)&i;
    p[0]=p[1]=1;
    
    if (i == 2 + (unsigned short)(-1))
    {
      // you'd expect this to execute, but it need not
    }
    
    if (i == 0)
    {
      // you'd expect this not to execute, but it actually may do so
    }
    

    请注意 char* 有一个例外,因此可以使用 char* 操作值(虽然不是很便携) .

  • 2

    你是对的,理智的 . 通常,指针只是一个地址,因此您可以将其转换为整数并执行任何算术运算 .

    但有时指针只是地址的一部分 . 在某些体系结构中,指针被转换为添加了base或另一个CPU寄存器的地址 .

    但是现在,在具有平坦内存模型和本地编译的C语言的PC和ARM架构上,可以认为指针是一维可寻址RAM中某个位置的整数地址 .

  • 3

    指针只是另一个变量,用于保存内存位置的地址(通常是另一个变量的内存地址) .

  • 5

    这里's how I' ve过去曾向一些困惑的人解释过:指针有两个影响其行为的属性 . 它有一个值,在(在典型环境中)是一个内存地址,一个类型,它告诉你它指向的对象的类型和大小 .

    例如,给定:

    union {
        int i;
        char c;
    } u;
    

    你可以有三个不同的指针都指向同一个对象:

    void *v = &u;
    int *i = &u.i;
    char *c = &u.c;
    

    如果比较这些指针的值,它们都是相等的:

    v==i && i==c
    

    但是,如果递增每个指针,您将看到它们指向的类型变得相关 .

    i++;
    c++;
    // You can't perform arithmetic on a void pointer, so no v++
    i != c
    

    变量 ic 此时将具有不同的值,因为 i++ 使 i 包含下一个可访问整数的地址, c++ 使 c 指向下一个可寻址字符 . 通常情况下,整数占用的内存多于字符数,因此 i 在递增后会以比 c 更大的值结束 .

  • 6

    与C中的任何其他变量一样,指针基本上是位的集合,其可以由一个或多个连接的 unsigned char 值表示(与任何其他类型的cariable一样, sizeof(some_variable) 将指示 unsigned char 值的数量) . 使指针与其他变量不同的原因是C编译器会将指针中的位解释为以某种方式标识可以存储变量的位置 . 在C中,与其他一些语言不同,可以为多个变量请求空间,然后将指针转换为该集合中的任何值,指向该集合中的任何其他变量 .

    许多编译器通过使用它们的位存储实际的机器地址来实现指针,但这不是唯一可能的实现 . 实现可以保留一个数组 - 用户代码无法访问 - 列出程序正在使用的所有内存对象(变量集)的硬件地址和分配大小,并且每个指针包含一个数组的索引与该索引的偏移量 . 这样的设计将允许系统不仅将代码限制为仅对其拥有的内存进行操作,而且还确保指向一个内存项的指针不会被意外地转换为指向另一个内存项的指针(在使用硬件的系统中)地址,如果 foobar 是连续存储在内存中的10个项目的数组,则指向 foo 的"eleventh"项目的指针可能改为指向 bar 的第一项,但在系统中,每个"pointer"是对象ID和偏移量,如果代码试图将指针指向超出其分配范围的 foo ,系统可能会陷阱 . 这样的系统也可以消除存储器碎片问题,因为可以移动与任何指针相关联的物理地址 .

    请注意,虽然指针有点抽象,但它们不够抽象,不允许完全符合标准的C编译器实现垃圾收集器 . C编译器指定每个变量(包括指针)都表示为 unsigned char 值的序列 . 给定任何变量,可以将其分解为数字序列,然后将该数字序列转换回原始类型的变量 . 因此,程序可能会有一些存储空间(接收指向它的指针),在那里存储东西,将指针分解为一系列字节,在屏幕上显示这些字节,然后擦除对它们的所有引用 . 如果程序然后从键盘接受了一些数字,将它们重构为指针,然后尝试从该指针读取数据,如果用户输入了相同的数字程序先前显示的数字,程序将需要输出已存储在 calloc '内存中的数据 . 由于没有可想到的方式,计算机可以知道用户是否已经制作了所显示的数字的副本,因此计算机不可能知道将来是否可能访问上述存储器 .

  • 3

    我不确定你的来源,但你所描述的语言类型来自C标准:

    6.5.3.2地址和间接运算符[...] 3.一元&运算符产生其操作数的地址 . [...]

    所以...是的,指针指向内存地址 . 至少这就是C标准所暗示的意思 .

    更清楚地说,指针是一个保存某个地址值的变量 . 使用一元 & 运算符返回对象的地址(可以存储在指针中) .

    我可以将地址“42 Wallaby Way,Sydney”存储在变量中(并且该变量将是各种类型的“指针”,但由于这不是内存地址,因此我们不能正确地称之为“指针”) . 您的计算机具有其内存桶的地址 . 指针存储地址的值(即指针存储值“42 Wallaby Way,Sydney”,这是一个地址) .

    Edit: 我想扩展Alexey Frunze的评论 .

    什么是指针?我们来看看C标准:

    6.2.5类型[...] 20. [...]指针类型可以从函数类型或对象类型派生,称为引用类型 . 指针类型描述一个对象,其值提供对引用类型的实体的引用 . 从引用类型T派生的指针类型有时被称为“指向T”的指针 . 从引用类型构造指针类型称为“指针类型派生” . 指针类型是完整的对象类型 .

    本质上,指针存储一个值,该值提供对某个对象或函数的引用 . 的种类 . 指针旨在存储一个值,该值提供对某些对象或函数的引用,但情况并非总是如此:

    6.3.2.3指针[...] 5.整数可以转换为任何指针类型 . 除非先前指定,否则结果是实现定义的,可能未正确对齐,可能不指向引用类型的实体,并且可能是陷阱表示 .

    上面的引用说我们可以将整数转换为指针 . 如果我们这样做(也就是说,如果我们将整数值填充到指针而不是对对象或函数的特定引用),那么指针“可能不会指向引用类型的实体”(即它可能不提供引用对象或函数) . 它可能会为我们提供其他东西 . 这是一个你可能在指针中粘贴某种句柄或ID的地方(即指针没有指向一个对象;它存储的是一个代表某个东西的值,但该值可能不是一个地址) .

    所以是的,正如Alexey Frunze所说,指针可能没有将地址存储到对象或函数中 . 指针可能存储某种“句柄”或ID,您可以通过为指针指定一些任意整数值来实现 . 此句柄或ID表示的内容取决于系统/环境/上下文 . 只要您的系统/实现能够理解 Value ,您就会处于良好的状态(但这取决于具体的 Value 和具体的系统/实施) .

    通常,指针存储对象或函数的地址 . 如果它没有存储实际地址(对象或函数),则结果是实现定义的(意味着确切地发生了什么以及指针现在表示什么取决于您的系统和实现,因此它可能是一个句柄或ID一个特定的系统,但在另一个系统上使用相同的代码/值可能会导致程序崩溃) .

    结果比我想象的还要长......

  • 7

    将指针视为地址是 approximation . 像所有近似值一样,'s good enough to be useful sometimes, but it'也不准确,这意味着依赖它会导致麻烦 .

    指针就像一个地址,它指示在哪里找到一个对象 . 这种类比的一个直接限制是并非所有指针实际上都包含一个地址 . NULL 是一个不是地址的指针 . 指针变量的内容实际上可以是以下三种之一:

    • 对象的 address ,可以解除引用(如果 p 包含 x 的地址,则表达式 *p 具有与 x 相同的值);

    • a null pointer ,其中 NULL 就是一个例子;

    • invalid 内容,它不指向一个对象(如果 p 不包含有效值,则 *p 可以执行任何操作(“未定义的行为”),同时使程序崩溃相当普遍) .

    此外,更准确地说,指针(如果有效且非空)包含地址:指针指示在哪里找到对象,但是有更多的信息与它相关联 .

    特别是,指针具有类型 . 在大多数平台上,指针的类型在运行时没有影响,但它在编译时具有超出类型的影响 . 如果 p 是指向 intint *p; )的指针,则 p + 1 指向 p 之后为 sizeof(int) 个字节的整数(假设 p + 1 仍然是有效指针) . 如果 q 是指向 char 的指针,指向与 pchar *q = p; )相同的地址,则 q + 1p + 1 的地址不同 . 如果您将指针视为地址,则对于指向同一位置的不同指针,“下一个地址”不同是不太直观的 .

    在某些环境中,有可能具有多个指针值,这些指针值具有指向存储器中相同位置的不同表示(存储器中的不同位模式) . 您可以将这些视为具有相同地址的不同指针,或同一位置的不同地址 - 在这种情况下隐喻并不清楚 . == 运算符总是告诉您两个操作数是否指向同一位置,因此在这些环境中,即使 pq 具有不同的位模式,也可以 p == q .

    甚至存在指针在地址之外携带其他信息的环境,例如类型或许可信息 . 您可以轻松地完成作为程序员的生活,而不会遇到这些问题 .

    存在不同种类的指针具有不同表示的环境 . 您可以将其视为具有不同表示形式的不同类型的地址 . 例如,某些体系结构具有字节指针和字指针,或对象指针和函数指针 .

    总而言之,只要记住这一点,将指针视为地址并不算太糟糕

    • 它只是有效的非空指针,是地址;

    • 您可以为同一位置设置多个地址;

    • 你可以't do arithmetic on addresses, and there'没有订单;

    • 指针也携带类型信息 .

    反过来更麻烦 . Not everything that looks like an address can be a pointer . 在任何指针深处的某个地方都表示为可以作为整数读取的位模式,你可以说这个整数是一个地址 . 但另一方面,并非每个整数都是指针 .

    首先有一些众所周知的局限;例如,一个指定程序外部位置的整数's address space can' t是一个有效的指针 . 未对齐的地址不会为需要对齐的数据类型生成有效指针;例如,在 int 需要4字节对齐的平台上,0x7654321不能是有效的 int* 值 .

    但是,它远远超出了这个范围,因为当你将指针变成一个整数时,你就陷入了困境 . 这个问题的一个重要原因是优化编译器在微优化方面要比大多数程序员预期的好得多,因此他们对程序如何工作的心理模型是非常错误的 . 仅仅因为你有相同地址的指针并不意味着它们是等价的 . 例如,请考虑以下代码段:

    unsigned int x = 0;
    unsigned short *p = (unsigned short*)&x;
    p[0] = 1;
    printf("%u = %u\n", x, *p);
    

    您可能期望在一台普通的机器上 sizeof(int)==4sizeof(short)==2 ,这可以打印 1 = 1? (little-endian)或 65536 = 1? (big-endian) . 但是在我使用GCC 4.4的64位Linux PC上:

    $ c99 -O2 -Wall a.c && ./a.out 
    a.c: In function ‘main’:
    a.c:6: warning: dereferencing pointer ‘p’ does break strict-aliasing rules
    a.c:5: note: initialized from here
    0 = 1?
    

    在这个简单的例子中,GCC非常友好 - 在更复杂的例子中,编译器可能没有注意到 . 由于 p&x 具有不同的类型,因此更改 p 指向的内容不会影响 &x 指向的内容(在一些明确定义的异常之外) . 因此,编译器可以自由地将 x 的值保存在寄存器中,而不会在 *p 更改时更新该寄存器 . 该程序取消引用指向同一地址的两个指针,并获得两个不同的值!

    这个例子的道德是,只要你遵守C语言的精确规则,将(非空有效)指针视为地址就可以了 . 硬币的另一面是C语言的规则是错综复杂的,除非你知道幕后发生了什么,否则难以获得直观的感觉 . 而在幕后发生的事情是,指针和地址之间的联系有点松散,既支持“异国情调”的处理器架构,又支持优化编译器 .

    因此,将指针视为理解的第一步,但不要过多地遵循这种直觉 .

  • 4

    BRIEF SUMMARY (我也将在顶部):

    (0)将指针视为地址通常是一种很好的学习工具,并且通常是指向普通数据类型的指针的实际实现 .

    (1)但是在许多(也许是大多数)编译器指向函数的指针不是地址,而是大于地址(通常是2x,有时更多),或者实际上是指向内存中的结构的指针,而不是包含函数的地址和类似的东西一个恒定的池 .

    (2)指向数据成员和指针的指针方法的指针往往更奇怪 .

    (3)具有FAR和NEAR指针问题的旧版x86代码

    (4)几个例子,最着名的是IBM AS / 400,带有安全的“胖指针” .

    我相信你能找到更多 .

    DETAIL:

    UMMPPHHH !!!!!到目前为止,许多答案都是相当典型的“程序员weenie”答案 - 但不是编译器weenie或硬件weenie . 因为我假装是硬件weenie,并且经常使用编译器weenies,让我投入我的两分钱:

    在许多(可能是大多数)C编译器上,指向 T 类型数据的指针实际上是 T 的地址 .

    精细 .

    但是,即使在许多这些编译器中,某些指针也不是地址 . 您可以通过查看 sizeof(ThePointer) 来判断这一点 .

    例如,指向函数的指针有时比普通地址大很多 . 或者,它们可能涉及一定程度的间接 . This article提供了一个涉及Intel Itanium处理器的描述,但我见过其他人 . 通常,要调用函数,您不仅必须知道函数代码的地址,还要知道函数常量池的地址 - 一个内存区域,通过单个加载指令从中加载常量,而不是编译器必须生成几个Load Immediate和Shift和OR指令中的64位常量 . 因此,您需要2个64位地址,而不是单个64位地址 . 一些ABI(应用程序二进制接口)将其移动为128位,而其他ABI使用间接级别,其中函数指针实际上是包含刚刚提到的2个实际地址的函数描述符的地址 . 哪个更好?取决于您的观点:性能,代码大小和一些兼容性问题 - 通常代码假定指针可以转换为long或long long,但也可以假设long long正好是64位 . 此类代码可能不符合标准,但客户可能希望它能够运行 .

    我们中的许多人对旧的Intel x86分段架构有着痛苦的回忆,其中包括NEAR POINTER和FAR POINTERS . 值得庆幸的是,现在几乎已经绝迹,所以只有一个简短的总结:在16位实模式中,实际的线性地址是

    LinearAddress = SegmentRegister[SegNum].base << 4 + Offset
    

    而在保护模式下,它可能是

    LinearAddress = SegmentRegister[SegNum].base + offset
    

    根据段中设置的限制检查生成的地址 . 有些程序使用的不是标准的C / C FAR和NEAR指针声明,但是很多只是说 *T ---但是有编译器和链接器开关所以,例如,代码指针可能在指针附近,只是32位偏移对任何是在CS(代码段)寄存器中,数据指针可能是FAR指针,为48位值指定16位段号和32位偏移量 . 现在,这两个数量肯定与地址有关,但由于它们的大小不同,它们中的哪一个是地址?此外,除了与实际地址相关的内容之外,这些段还带有权限 - 只读,读写,可执行 - .

    一个更有趣的例子,恕我直言,是(或许是)IBM AS / 400系列 . 该计算机是最早在C中实现操作系统的计算机之一 . 该机器上的指针通常是实际地址大小的2倍 - 例如正如this presentation所说,128位指针,但实际地址是48-64位,而且,一些额外的信息,所谓的功能,提供读,写等权限,以及防止缓冲区溢出的限制 . 是的:你可以与C / C兼容 - 如果这种情况无处不在,中国人民解放军和斯拉夫黑手党就不会侵入太多的西方计算机系统 . 但从历史上看,大多数C / C编程都忽略了性能安全性 . 最有趣的是,AS400系列允许操作系统创建安全指针,这些指针可以提供给非特权代码,但是无特权代码无法伪造或篡改 . 同样,安全性,并且在符合标准的情况下,非常草率的非标准兼容的C / C代码将无法在这样的安全系统中工作 . 同样,有官方标准,并有事实上的标准 .

    现在,我将离开我的安全肥皂盒,并提到一些指针(各种类型)通常不是真正地址的其他方式:指向数据成员的指针,指向成员函数方法的指针及其静态版本大于普通地址 . 正如this post所说:

    有很多方法可以解决这个问题[与单一与多重的继承和虚拟继承相关的问题] . 以下是Visual Studio编译器决定处理它的方法:指向乘法继承类的成员函数的指针实际上是一个结构 . “然后他们继续说”转换函数指针可以改变它的大小!“ .

    正如您可能从我对安全性的指责中猜到的那样,我参与了C / C硬件/软件项目,其中指针被视为功能而不是原始地址 .

    我可以继续,但我希望你能得到这个想法 .

    BRIEF SUMMARY (我也会把它放在顶端):

    (0)将指针视为地址通常是一种很好的学习工具,并且通常是指向普通数据类型的指针的实际实现 .

    (1)但是在许多(也许是大多数)编译器指向函数的指针不是地址,而是大于地址(通常是2X,有时更多),或者实际上是指向内存中的结构的指针而不是包含函数的地址和类似的东西一个恒定的池 .

    (2)指向数据成员和指向方法的指针往往更加陌生 .

    (3)具有FAR和NEAR指针问题的旧版x86代码

    (4)几个例子,最着名的是IBM AS / 400,带有安全的“胖指针” .

    我相信你能找到更多 .

  • 31

    在理解指针之前,我们需要了解对象 . 对象是存在的实体,并且具有称为地址的位置说明符 . 指针只是一个变量,就像 C 中的任何其他变量一样,具有一个名为 pointer 的类型,其内容被解释为支持以下操作的对象的地址 .

    + : A variable of type integer (usually called offset) can be added to yield a new pointer
    - : A variable of type integer (usually called offset) can be subtracted to yield a new pointer
      : A variable of type pointer can be subtracted to yield an integer (usually called offset)
    * : De-referencing. Retrieve the value of the variable (called address) and map to the object the address refers to.
    ++: It's just `+= 1`
    --: It's just `-= 1`
    

    指针根据它当前引用的对象类型进行分类 . 它重要的信息的唯一部分是对象的大小 .

    任何对象都支持一个操作 & (地址),它将对象的位置说明符(地址)作为指针对象类型进行检索 . 这应该可以减少围绕命名法的混淆,因为将 & 称为对象的操作而不是指针,其结果类型是对象类型的指针 .

    Note 在整个解释中,我忽略了记忆的概念 .

  • 0

    好吧,指针是表示内存位置的 abstraction . 请注意,引用并没有说将指针视为内存地址是错误的,它只是说它是"usually leads to grief" . 换句话说,它会导致你有不正确的期望 .

    最可能的悲痛来源当然是 pointer arithmetic, ,这实际上是C 's strengths. If a pointer was an address, you' d期望指针算法成为地址算术之一;但事实并非如此 . 例如,向地址添加10应该为您提供一个大于10个寻址单位的地址;但是向指针添加10会使其增加10倍于它所指向的对象的大小(甚至不是实际大小,而是向上舍入到对齐边界) . 在具有32位整数的普通架构上使用 int * ,向其中添加10将使其增加40个寻址单位(字节) . 经验丰富的C程序员已经意识到这一点并与之共存,但你的作者显然不喜欢草率的隐喻 .

    还有一个额外的问题 how the contents of the pointer represent the memory location: 正如许多答案所解释的那样,地址并不总是一个int(或长) . 在某些体系结构中,地址是"segment"加上偏移量 . 指针甚至可能只包含当前段("near"指针)的偏移量,它本身不是唯一的内存地址 . 并且指针内容可能与硬件理解的内存地址只有间接关系 . 但引用引用的作者甚至没有提到代表性,所以我认为这是概念上的等同性,而不是代表性 .

  • 4

    考虑一下,我认为这是正确的,因为C标准指的是一个指针,如同其他人已在此处提到的那样,为引用的对象保存一个地址 . 但是,地址!=内存地址 . 一个地址可以是任何符合C标准的东西,虽然它最终会导致一个内存地址,指针本身可以是一个id,一个偏移选择器(x86),实际上只要它可以描述(映射后)任何内存地址在可寻址的空间 .

  • 2

    指针值是地址 . 指针变量是可以存储地址的对象 . 这是正确的,因为对于C新手而言,这很重要,因为C新手通常不清楚指针与它指向的东西之间的区别(也就是说,它们不是指针存储的东西)很重要,因为它排除了这一点 .

    但是,标准在特定的抽象层次上进行讨论 . 那些作者谈到谁“知道什么地址是关于”,但谁是C的新手,必须在不同的抽象层次上学习地址 - 也许是通过编程汇编语言 . 无法保证C实现使用与CPU操作码使用相同的地址表示(在本段中称为“存储地址”),这些人已经知道 .

    他继续谈论"perfectly reasonable address manipulation" . 就C标准而言,'s basically no such thing as 430441 . Addition is defined on pointers and that is basically it. Sure, you can convert a pointer to integer, do some bitwise or arithmetic ops, and then convert it back. This is not guaranteed to work by the standard, so before writing that code you'd更好地了解您的特定C实现如何表示指针并执行该转换 . 它可能使用了你期望的地址表示,但是它没有你的错,因为你没有混淆,这是不正确的编程程序;-)

    简而言之,C使用了更抽象的概念地址比作者的地址 .

    作者's concept of an address of course is also not the lowest-level word on the matter. What with virtual memory maps and physical RAM addressing across multiple chips, the number that you tell the CPU is 430445 you want to access has basically nothing to do with where the data you want is actually located in hardware. It'的所有层次的间接和表示,但作者选择了一个特权 . 如果您在谈论C时要这样做,请选择C级别以获得特权!

    就个人而言,除了在向汇编程序员介绍C语言的情况下,我认为作者的评论并不是那么有用 . 对于来自更高级语言的人来说,指针值不是地址肯定没有用 . 承认复杂性要好得多,而不是说CPU具有垄断说明地址是什么,因此C指针值“不是”地址 . 它们是地址,但它们可能用与其意思的地址不同的语言编写 . 我认为,将C语境中的两件事区分为“地址”和“商店地址”就足够了 .

  • 8

    很难准确地说出这些书的作者究竟是什么意思 . 指针是否包含地址取决于您如何定义地址以及如何定义指针 .

    从所有写入的答案来看,有些人认为(1)地址必须是整数,(2)指针不需要通过虚拟而不是在说明书中这样说 . 有了这些假设,那么明确的指针不一定包含地址 .

    然而,我们看到虽然(2)可能是真的,(1)可能不一定是真的 . 根据@ CornStalks的回答,该怎样的&被称为运算符的地址?这是否意味着规范的作者想要一个包含地址的指针?

    那么我们可以说,指针包含一个地址,但地址不一定是整数吗?也许 .

    我认为所有这些都是胡言乱语的迂腐语义 . 实际上,这完全没有 Value . 你能想到一个编译器以一种指针值不是地址的方式生成代码吗?如果是这样,什么?那正是我所想...

    我认为本书的作者(声称指针不一定只是地址的第一个摘录)可能指的是指针带有固有的类型信息 .

    例如,

    int x;
     int* y = &x;
     char* z = &x;
    

    y和z都是指针,但y 1和z 1是不同的 . 如果它们是内存地址,这些表达式不会给你相同的值吗?

    在这里谎言 thinking about pointers as if they were addresses usually leads to grief . 编写错误是因为人们认为指针好像是地址,这通常会导致悲伤 .

    55555可能不是指针,虽然它可能是一个地址,但是(int *)55555是一个指针 . 55555 1 = 55556,但是(int *)55555 1是55559(/ - sizeof(int)方面的差异) .

  • 3

    快速摘要:C地址是一个值,通常表示为具有特定类型的计算机级内存地址 .

    不合格的单词"pointer"含糊不清 . C具有指针对象(变量),指针类型,指针表达式和指针值 .

    使用“指针”这个词来表示“指针对象”是很常见的,这可能会导致一些混淆 - 这就是为什么我尝试使用“指针”作为形容词而不是名词 .

    至少在某些情况下,C标准使用"pointer"这个词来表示"pointer value" . 例如, malloc 的描述为"returns either a null pointer or a pointer to the allocated space" .

    那么C中的地址是什么?它是指针值,即某个特定指针类型的值 . (除了空指针值不一定称为“地址”,因为它不是任何地址) .

    标准对一元 & 运算符的描述称"yields the address of its operand" . 在C标准之外,单词"address"通常用于表示(物理或虚拟)存储器地址,通常是一个字的大小(无论"word"在给定系统上是什么) .

    C "address"通常实现为机器地址 - 就像C int 值通常实现为机器字一样 . 但是C地址(指针值)不仅仅是一个机器地址 . 它通常表示为机器地址,它是具有某种特定类型的值 .

  • 14

    指针是一种在C / C中本机可用的变量类型,包含一个内存地址 . 与任何其他变量一样,它具有自己的地址并占用内存(数量是特定于平台的) .

    您将看到由于混淆而导致的一个问题是尝试通过简单地按值传递指针来更改函数内的指示对象 . 这将在函数作用域中复制指针,并且对新指针“指向”的位置的任何更改都不会更改调用该函数的作用域中指针的指示对象 . 为了修改函数内的实际指针,通常会将指针传递给指针 .

  • 38

    指针只是另一个变量,它可以包含通常另一个变量的内存地址 . 指针是一个变量,它也有一个内存地址 .

  • 0

    Mark Bessey已经说过了,但这需要再次强调,直到了解 .

    指针与变量有很大关系,而不是文字3 .

    指针是值(地址)和类型(具有附加属性,如只读)的元组 . 类型(以及附加参数,如果有的话)可以进一步定义或限制上下文;例如 . __far ptr, __near ptr :地址的上下文是什么:堆栈,堆,线性地址,偏离某处,物理内存或什么 .

    它是类型的属性,使指针算术与整数算术略有不同 .

    不是变量的指针的计数器示例太多而不能忽略

    • fopen返回一个FILE指针 . (这是变量)

    • 堆栈指针或帧指针通常是不可寻址的寄存器

    *(int *)0x1231330 = 13; - 将任意整数值转换为pointer_of_integer类型并写入/读取整数而不引入变量

    在C程序的生命周期中,会有许多其他临时指针实例没有地址 - 因此它们不是变量,而是具有编译时关联类型的表达式/值 .

  • 141

    地址用于标识一块固定大小的存储,通常用于每个字节,作为整数 . 这被称为字节地址,它也被ISO C使用 . 可以有一些其他方法来构造地址,例如,对于每一位 . 但是,通常只使用字节地址,我们通常省略"byte" .

    从技术上讲,地址永远不是C中的值,因为(ISO)C中术语“值”的定义是:

    当解释为具有特定类型时,对象内容的精确含义

    (由我强调 . )但是,C中没有这样的“地址类型” .

    指针不一样 . 指针是C语言中的一种类型 . 有几种不同的指针类型 . 它们不一定遵守相同的语言规则,例如 ++ 对类型 int*char* 的值的影响 .

    C中的值可以是指针类型 . 这称为指针值 . 要清楚,指针值不是C语言中的指针 . 但我们习惯于将它们混合在一起,因为在C中它不太可能是模糊的:如果我们将表达式 p 称为"pointer",它只是一个指针值而不是一个类型,因为C中的命名类型不表达通过表达式,但通过类型名称或typedef名称 .

    其他一些东西很微妙 . 作为C用户,首先,应该知道 object 的含义:

    执行环境中的数据存储区域,其内容可以表示值

    对象是表示具有特定类型的值的实体 . 指针是对象类型 . 因此,如果我们声明 int* p; ,则 p 表示"an object of pointer type",或"pointer object" .

    注意,标准规范地定义了 no "variable"(实际上它在规范性文本中从未被ISO C用作名词) . 但是,非正式地,我们将对象称为变量,就像其他语言一样 . (但仍然不那么准确,例如在C中,变量可以是规范性的引用类型,这不是对象 . )短语"pointer object"或"pointer variable"有时被视为如上所述的"pointer value",可能略有不同 . (还有一组例子是"array" . )

    由于指针是一种类型,并且地址在C中实际上是"typeless",因此指针值大致为"contains"一个地址 . 并且指针类型的表达式可以产生地址,例如,

    ISO C11 6.5.2.3

    3一元&运算符产生其操作数的地址 .

    注意,该措辞由WG14 / N1256引入,即ISO C99:TC3 . 在C99有

    3一元&运算符返回其操作数的地址 .

    它反映了委员会的意见:地址是 not 由一元 & 运算符返回的指针值 .

    尽管有上述措辞,但标准中仍然存在一些混乱 .

    ISO C11 6.6

    9地址常量是空指针,指向左值的指针,指定静态存储持续时间的对象,或指向函数指示符的指针

    ISO C++11 5.19

    3 ...地址常量表达式是指针类型的prvalue核心常量表达式,它表示具有静态存储持续时间,对象函数地址或空指针值或prvalue核心常量的对象的地址表达式类型为std :: nullptr_t . ...

    (最近的C标准草案使用了另一种措辞,所以没有这个问题 . )

    实际上,C中的“地址常量”和C中的“地址常量表达式”都是指针类型的常量表达式(或者至少是自C 11以来的“指针式”类型) .

    内置的一元 & 运算符在C和C中被称为"address-of";类似地, std::addressof 在C 11中引入 .

    这些命名可能会带来误解 . 结果表达式是指针类型,因此它们被解释为:结果包含/产生地址,而不是地址 .

相关问题