首页 文章

什么“取消引用”指针意味着什么?

提问于
浏览
425

请提供一个解释示例 .

6 回答

  • 602

    复习基本术语

    它通常足够好 - 除非你是编程程序集 - 设想一个包含数字内存地址的 pointer ,其中1表示进程内存中的第二个字节,2表示第三个字节,3表示第四个字段等等....

    • 0和第一个字节发生了什么变化?好吧,我们稍后会讨论 - 请参阅下面的空指针 .

    • 有关指针存储内容的更准确定义,以及内存和地址的关系,请参阅"More about memory addresses, and why you probably don't need to know" .

    当你想访问指针所指向的内存中的数据/值 - 具有该数字索引的地址的内容 - 那么你指针是_430192 .

    不同的计算机语言有不同的符号告诉编译器或解释器您现在对指向的值感兴趣 - 我将重点放在C和C上 .

    一个指针场景

    在C中考虑,给出一个指针如下面的 p ......

    const char* p = "abc";
    

    ...四个字节,其中数字值用于编码字母'a','b','c',0字节表示文本数据的结尾,存储在内存中的某个位置,该数据的数字地址存储在 p 中 .

    例如,如果字符串文字恰好位于地址0x1000和 p 处是一个位于0x2000的32位指针,则内存内容将为:

    Memory Address (hex)    Variable name    Contents
    1000                                     'a' == 97 (ASCII)
    1001                                     'b' == 98
    1002                                     'c' == 99
    1003                                     0
    ...
    2000-2003               p                1000 hex
    

    请注意,地址0x1000没有变量名称/标识符,但我们可以使用存储其地址的指针间接引用字符串文字: p .

    取消引用指针

    要引用 p 指向的字符,我们使用其中一种表示法取消引用 p (同样,对于C):

    assert(*p == 'a');  // The first character at address p will be 'a'
    assert(p[1] == 'b'); // p[1] actually dereferences a pointer created by adding
                         // p and 1 times the size of the things to which p points:
                         // In this case they're char which are 1 byte in C...
    assert(*(p + 1) == 'b');  // Another notation for p[1]
    

    您还可以移动指向数据的指针,随时取消引用它们:

    ++p;  // Increment p so it's now 0x1001
    assert(*p == 'b');  // p == 0x1001 which is where the 'b' is...
    

    如果您有一些可以写入的数据,那么您可以执行以下操作:

    int x = 2;
    int* p_x = &x;  // Put the address of the x variable into the pointer p_x
    *p_x = 4;       // Change the memory at the address in p_x to be 4
    assert(x == 4); // Check x is now 4
    

    在上面,你必须在编译时知道你需要一个名为 x 的变量,并且代码要求编译器安排它应该存储的位置,确保地址可以通过 &x 获得 .

    取消引用和访问结构数据成员

    在C中,如果您有一个变量是指向具有数据成员的结构的指针,则可以使用 -> dereferencing运算符访问这些成员:

    typedef struct X { int i_; double d_; } X;
    X x;
    X* p = &x;
    p->d_ = 3.14159;  // Dereference and access data member x.d_
    (*p).d_ *= -1;    // Another equivalent notation for accessing x.d_
    

    多字节数据类型

    要使用指针,计算机程序还需要深入了解所指向的数据类型 - 如果该数据类型需要多个字节来表示,则指针通常指向数据中编号最小的字节 .

    所以,看一个稍微复杂的例子:

    double sizes[] = { 10.3, 13.4, 11.2, 19.4 };
    double* p = sizes;
    assert(p[0] == 10.3);  // Knows to look at all the bytes in the first double value
    assert(p[1] == 13.4);  // Actually looks at bytes from address p + 1 * sizeof(double)
                           // (sizeof(double) is almost always eight bytes)
    assert(++p);           // Advance p by sizeof(double)
    assert(*p == 13.4);    // The double at memory beginning at address p has value 13.4
    *(p + 2) = 29.8;       // Change sizes[3] from 19.4 to 29.8
                           // Note: earlier ++p and + 2 here => sizes[3]
    

    指向动态分配内存的指针

    有时你不需要直到你的程序运行并看到它抛出了什么数据......然后你可以使用 malloc 动态分配内存 . 通常的做法是将地址存储在指针中......

    int* p = malloc(sizeof(int)); // Get some memory somewhere...
    *p = 10;            // Dereference the pointer to the memory, then write a value in
    fn(*p);             // Call a function, passing it the value at address p
    (*p) += 3;          // Change the value, adding 3 to it
    free(p);            // Release the memory back to the heap allocation library
    

    在C中,内存分配通常使用 new 运算符完成,并使用 delete 重新分配:

    int* p = new int(10); // Memory for one int with initial value 10
    delete p;
    
    p = new int[10];      // Memory for ten ints with unspecified initial value
    delete[] p;
    
    p = new int[10]();    // Memory for ten ints that are value initialised (to 0)
    delete[] p;
    

    另请参阅下面的C智能指针 .

    地址丢失和泄漏

    通常,指针可能是内存中存在某些数据或缓冲区的唯一指示 . 如果需要持续使用该数据/缓冲区,或者能够调用 free()delete 以避免泄漏内存,那么程序员必须对指针的副本进行操作......

    const char* p = asprintf("name: %s", name);  // Common but non-Standard printf-on-heap
    
    // Replace non-printable characters with underscores....
    for (const char* q = p; *q; ++q)
        if (!isprint(*q))
            *q = '_';
    
    printf("%s\n", p); // Only q was modified
    free(p);
    

    ...或小心地协调任何变化的逆转......

    const size_t n = ...;
    p += n;
    ...
    p -= n;  // Restore earlier value...
    

    C智能指针

    在C中,最佳做法是使用smart pointer对象来存储和管理指针,在智能指针的析构函数运行时自动解除分配 . 从C11开始,标准库提供了两个unique_ptr,用于分配对象的单个所有者时...

    {
        std::unique_ptr<T> p{new T(42, "meaning")};
        call_a_function(p);
        // The function above might throw, so delete here is unreliable, but...
    } // p's destructor's guaranteed to run "here", calling delete
    

    ...和shared_ptr共享所有权(使用reference counting)...

    {
        std::shared_ptr<T> p(new T(3.14, "pi"));
        number_storage.may_add(p); // Might copy p into its container
    } // p's destructor will only delete the T if number_storage didn't copy
    

    空指针

    在C中, NULL0 - 以及另外在C nullptr 中 - 可用于指示指针不被取消引用或用于指针算术 . 例如:

    const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
    char c;
    while ((c = getopt(argc, argv, "f:")) != EOF)
        switch (c) {
          case f: p_filename = optarg; break;
        }
    if (p_filename)  // Only NULL converts to false
        ...   // Only get here if -f flag specified
    

    在C和C中,正如内置数值类型不一定默认为 0bools 也不是 false ,指针并不总是设置为 NULL . 所有这些都设置为0 / false / NULL,当它们是 static 变量或(仅C)静态对象或其基础的直接或间接成员变量,或者进行零初始化(例如 new T();new T(x, y, z); 对T的成员执行零初始化,包括指针,而 new T; 没有) .

    此外,当您将 0NULLnullptr 分配给指针时,指针中的位不一定全部复位:指针可能不包含硬件级别的"0",或者指向虚拟地址空间中的地址0 . 如果有理由,编译器可以在那里存储其他东西,但无论它做什么 - 如果你来的话比较指向 0NULLnullptr 或指定了其中任何指针的指针,比较必须按预期工作 . 因此,在编译器级别的源代码下面,"NULL"在C和C语言中可能有点"magical" ...

    有关内存地址的更多信息,以及您可能不需要知道的原因

    更严格地说,初始化指针存储一个位模式,用于标识 NULL 或(通常为virtual)内存地址 .

    简单的情况是这是进程整个虚拟地址空间的数字偏移量;在更复杂的情况下,指针可以相对于某个特定的存储区域,CPU可以根据CPU“段”寄存器或在位模式中编码的某种方式的段ID来选择,和/或根据不同的位置查看使用地址的机器代码指令 .

    例如, int* 正确初始化以指向 int 变量可能 - 在转换为 float* 之后 - 访问"GPU"内存中与 int 变量完全不同的值,然后一旦转换为函数指针可能会引用保存机器操作码的不同内存为了这个功能 .

    像C和C这样的3GL编程语言倾向于隐藏这种复杂性,例如:

    • 如果编译器为您提供了一个指向变量或函数的指针,您可以自由地取消引用它(只要该变量没有被解析/解除分配),并且编译器的问题是,例如,需要事先恢复特定的CPU寄存器,或使用不同的机器代码指令

    • 如果你得到一个指向数组中元素的指针,你可以使用指针运算来移动数组中的任何其他位置,甚至可以形成一个数组的地址,这个数据与其他指针相比是合法的 . 数组中的元素(或类似地通过指针算术移动到相同的一个过去的结束值);再次在C和C中,由编译器来确保这“正常工作”

    • 特定的OS功能,例如共享内存映射,可能会给你指针,并且它们只会在对它们有意义的地址范围内“正常工作”

    • 尝试将法律指针移到这些边界之外,或者将任意数字转换为指针,或者使用指向不相关类型的指针,通常都有undefined behaviour,因此在更高级别的库和应用程序中应该避免使用,但是应该为操作系统,设备驱动程序等编写代码 . 可能需要依赖C或C未定义的行为,但这些行为仍由其特定硬件定义 .

  • 7

    取消引用指针意味着获取存储在指针指向的内存位置的值 . 运算符*用于执行此操作,称为解除引用运算符 .

    int a = 10;
    int* ptr = &a;
    
    printf("%d", *ptr); // With *ptr I'm dereferencing the pointer. 
                        // Which means, I am asking the value pointed at by the pointer.
                        // ptr is pointing to the location in memory of the variable a.
                        // In a's location, we have 10. So, dereferencing gives this value.
    
    // Since we have indirect control over a's location, we can modify its content using the pointer. This is an indirect way to access a.
    
     *ptr = 20;         // Now a's content is no longer 10, and has been modified to 20.
    
  • 14

    简单来说,解除引用意味着从该指针指向的某个内存位置访问该值 .

  • 9

    代码和解释来自Pointer Basics

    取消引用操作从指针开始,然后跟随箭头以访问其指针 . 目标可能是查看指针状态或更改指针状态 . 指针上的取消引用操作仅在指针具有指针对象时才起作用 - 必须分配指针对象并且必须将指针设置为指向它 . 指针代码中最常见的错误是忘记设置指针 . 由于代码中的错误,最常见的运行时崩溃是失败的解除引用操作 . 在Java中,运行时系统会礼貌地标记错误的解除引用 . 在诸如C,C和Pascal之类的编译语言中,不正确的解除引用有时会崩溃,而其他时候会以某种微妙的随机方式破坏内存 . 由于这个原因,编译语言中的指针错误很难被追踪 .

    void main() {   
        int*    x;  // Allocate the pointer x
        x = malloc(sizeof(int));    // Allocate an int pointee,
                                // and set x to point to it
        *x = 42;    // Dereference x to store 42 in its pointee   
    }
    
  • 2

    指针是值的“引用”..很像库函数是对书的引用 . “解除引用”电话号码实际上是通过并检索该书 .

    int a=4 ;
    int *pA = &a ;
    printf( "The REFERENCE/call number for the variable `a` is %p\n", pA ) ;
    
    // The * causes pA to DEREFERENCE...  `a` via "callnumber" `pA`.
    printf( "%d\n", *pA ) ; // prints 4..
    

    如果这本书不存在,图书管理员就会开始大喊大叫,关闭图书馆,并且有几个人会调查一个人找不到书的原因 .

  • 84

    我认为之前的所有答案都是错误的,因为它们声明解除引用意味着访问实际值 . 维基百科给出了正确的定义:https://en.wikipedia.org/wiki/Dereference_operator

    它对指针变量进行操作,并返回一个等于指针地址值的l值 . 这称为“解除引用”指针 .

    也就是说,我们可以取消引用指针而无需访问它指向的值 . 例如:

    char *p = NULL;
    *p;
    

    我们取消引用NULL指针而不访问其值 . 或者我们可以这样做:

    p1 = &(*p);
    sz = sizeof(*p);
    

    再次,解除引用,但从不访问该值 . 此类代码不会崩溃:当您通过无效指针实际访问数据时会发生崩溃 . 但是,遗憾的是,根据标准,解除引用无效指针是一种未定义的行为(除了少数例外),即使您没有尝试触摸实际数据 .

    简而言之:取消引用指针意味着将解引用运算符应用于它 . 该运算符只返回l值以供将来使用 .

相关问题