什么是“取消引用”指针?

请附上解释的例子。

回顾基本术语

通常是足够好的 – 除非你正在编程assembly – 设想一个包含数字存储器地址的指针 ,其中1指向进程内存中的第二个字节,2第三个,第三个,第四个等等….

  • 0和第一个字节发生了什么? 那么稍后我们会看到 – 请参阅下面的空指针
  • 要更准确地定义指针存储的内容以及内存和地址的关系,请参阅“关于内存地址的更多信息,以及您可能不需要知道的原因”

当你想要访问指针指向的内存中的数据/值时,就是使用那个数字索引来访问地址的内容,然后解除指针的引用

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

指针场景

考虑在C中,给出一个像下面的p这样的指针。

 const char* p = "abc"; 

…用于编码字母“a”,“b”,“c”的数字值和用于表示文本数据结尾的0字节的四个字节被存储在存储器中的某处,并且数字地址数据存储在p

例如,如果string文字碰巧位于地址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没有variables名称/标识符,但我们可以使用存储其地址p的指针来间接引用string字面值。

解引用指针

为了引用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的variables,代码要求编译器安排它应该存储的地方,确保地址可以通过&x

解引用和访问结构数据成员

在C中,如果你有一个variables是一个指向具有数据成员的结构的指针,你可以使用-> 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_ 

多字节数据types

要使用指针,计算机程序还需要了解指向的数据types – 如果该数据types需要多个字节来表示,则指针通常指向数据中编号最小的字节。

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

 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] 

指针dynamic分配内存

有时你不知道在程序运行之前你需要多less内存,并且看到有什么数据被抛出,然后你可以使用mallocdynamic分配内存。 将地址存储在指针中是常见的做法。

 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 ++中,最好的做法是使用智能指针对象来存储和pipe理指针,当智能指针的析构函数运行时自动释放它们。 由于C ++ 11标准库提供了两个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共享所有权(使用引用计数 )…

 { 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都可以用来表示一个指针当前不包含variables的内存地址,不应该在指针运算中被解引用或使用。 例如:

 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 ++中,就像内置数字types不一定默认为0 ,也不一定是false ,指针并不总是设置为NULL 。 当它们是staticvariables或(仅限于C ++)静态对象的直接或间接成员variables或它们的基础,或者经历零初始化(例如new T(); new T(x, y, z);对包括指针在内的T的成员进行零初始化,而new T;则不)。

而且,当你将0NULLnullptr指定给一个指针时,指针中的位不一定全部复位:指针在硬件级别可能不包含“0”,或者指向虚拟地址空间中的地址0。 编译器被允许存储其他的东西,如果它有理由的话,但不pipe它是什么 – 如果你一起来比较指针0NULLnullptr或其他指定的指针,比较必须按照预期。 因此,在编译器级别的源代码下面,“NULL”在C和C ++语言中可能有点“神奇”。

更多关于内存地址,以及为什么你可能不需要知道

更严格地说,初始化的指针存储一个标识NULL或(通常是虚拟的 )内存地址的位模式。

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

例如,一个int*正确初始化为指向一个intvariables,在转换为float*之后,可能会在“GPU”内存中访问一个与intvariables完全不同的值,然后一旦强制转换为函数指针,就可能引用不同的内存拿着机器操作码的function。

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

  • 如果编译器给你一个指向variables或者函数的指针,你可以自由地对其进行解引用(只要variables没有被破坏/解除分配),这是编译器的问题,例如,是否需要事先恢复一个特定的CPU寄存器,机器代码指令使用

  • 如果你得到一个指向数组元素的指针,你可以使用指针运算来移动数组中的任何地方,甚至可以形成一个数组的合法地位,以便与其他指向元素的指针进行比较在数组中(或者通过指针运算被类似地移动到相同的一个最后的值); 再次在C和C ++中,由编译器来确保这个“正常工作”

  • 特定的操作系统function,例如共享内存映射可能会给你指针,他们将在地址范围内“正常工作”,这对他们是有意义的

  • 尝试将合法指针移动到这些边界之外,或者将任意数字转换为指针,或者使用指向不相关types的指针,通常会有未定义的行为 ,所以应该避免在更高级别的库和应用程序中使用,但是为操作系统,设备驱动程序等可能需要依赖C或C ++未定义的行为,这是由其特定的硬件决定的。

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

 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. 

指针是一个值的“引用”。很像图书馆电话号码是一本书的参考。 电话号码的“取消引用”实际上是通过检索该书。

 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.. 

如果书不在那里,图书pipe理员就会开始大声喊叫,closures图书馆,并且有几个人正在调查一个人去找一本不在那里的书的原因。

来自指针的代码和解释基础知识 :

取消引用操作从指针开始并按箭头键访问其指针。 目标可能是看指向对象状态或改变指针对象状态。 指针的解引用操作只有在指针具有指针时才起作用 – 指针必须被分配,并且指针必须被设置为指向它。 指针代码中最常见的错误是忘记设置指针。 由于代码中的错误,最常见的运行时崩溃是失败的取消引用操作。 在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 } 

简而言之,解引用意味着访问指针指向的特定内存位置的值。

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

它在一个指针variables上运行,并返回一个相当于指针地址值的l值。 这被称为“取消引用”指针。

也就是说,我们可以在不访问指向的值的情况下解引用指针。 例如:

 char *p = NULL; *p; 

我们在不访问其值的情况下解除了NULL指针的引用。 或者我们可以这样做:

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

同样,解引用,但从不访问值。 这样的代码不会崩溃:当您实际通过无效的指针访问数据时发生崩溃。 但是,不幸的是,根据标准,取消引用无效指针是一个未定义的行为(有一些例外),即使您不尝试触摸实际的数据。

所以简而言之:解引用指针意味着应用解引用运算符。 该运算符只是为您将来的使用返回一个l值。