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


540

请在说明中包含一个示例。




24
int *p;将定义一个指向整数的指针,并*p取消对该指针的引用,这意味着它实际上将检索p指向的数据。
Peyman

4
Binky的Pointer Fun(cslibrary.stanford.edu/104)是一个很棒的视频,介绍了可以澄清事物的指针。@ Erik-您为建立Stanford CS Library链接而感到震惊。那里有很多东西……
templatetypedef

6
哈里的回应与这里有用的相反。
Jim Balter

Answers:


731

复习基本术语

这是通常不够好-除非你正在编写组件-想象一个指针包含数字的内存地址,1指的是第二个字节的进程的内存,2个第三,3,第四等等....

  • 0和第一个字节发生了什么?好吧,我们稍后再讲-请参阅下面的空指针
  • 有关指针存储的内容以及内存和地址之间的关系的更准确定义,请参阅此答案末尾的“有关内存地址的更多信息,以及为什么可能不需要知道的信息”

当您要访问指针所指向的内存中的数据/值时-具有该数字索引的地址的内容-然后取消对指针的引用

不同的计算机语言具有不同的表示法,以告诉编译器或解释器您现在对指向对象的(当前)值感兴趣-我在下面重点介绍C和C ++。

指针方案

考虑在C中,给出p如下所示的指针...

const char* p = "abc";

...具有用于编码字母'a','b','c'的数值的四个字节以及表示文本数据结尾的0字节存储在内存中的某个位置,并且该地址的数字地址数据存储在中p。这种C在内存中对文本进行编码的方式称为ASCIIZ

例如,如果字符串文字恰好位于地址0x1000处,而p32位指针位于0x2000处,则内存内容将为:

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中,如果您有一个变量,该变量是指向具有数据成员的结构的指针,则可以使用->解引用运算符访问这些成员:

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)
++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 = (int*)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...
free(p);

C ++智能指针

在C ++中,最佳实践是使用智能指针对象来存储和管理指针,并在智能指针的析构函数运行时自动对其进行分配。自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股份所有权(使用引用计数)...

{
    auto p = std::make_shared<T>(3.14, "pi");
    number_storage1.may_add(p); // Might copy p into its container
    number_storage2.may_add(p); // Might copy p into its container    } // p's destructor will only delete the T if neither may_add copied it

空指针

在C中,NULL以及0-以及在C ++中,nullptr-可用于指示指针当前不保存变量的内存地址,并且不应在指针算术中将其取消引用或使用。例如:

const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
int c;
while ((c = getopt(argc, argv, "f:")) != -1)
    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 ++中,就像内置数字类型不一定默认为0,也不一定boolsfalse,指针不一定总是设置为NULL。当它们是static静态对象或其基址的变量或(仅C ++)直接或间接成员变量,或进行零初始化(例如new T();new T(x, y, z);对T的成员(包括指针)执行零初始化)时,所有这些均设置为0 / false / NULL。new T;才不是)。

此外,当您将和分配给指针时0,指针中的位不一定全部复位:指针在硬件级别上可能不包含“ 0”,或在您的虚拟地址空间中引用地址0。编译器允许存储别的东西存在,如果有理由,但无论它-如果你一起去和指针比较,,或者被分配任何那些另一个指针,比较,必须按预期方式工作。因此,在编译器级别的源代码之下,“ NULL”在C和C ++语言中可能有点“神奇”……NULLnullptr0NULLnullptr

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

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

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

例如,int*正确初始化为指向int变量的变量-在转换为float*-“ GPU”内存中的访问存储器后,可能与该变量所在的存储器完全不同int,然后一旦转换为函数指针并用作函数指针,它可能会指向进一步程序的不同内存保存机操作码(int*有效的数字值为这些其他内存区域内的随机,无效指针)。

像C和C ++这样的3GL编程语言往往掩盖了这种复杂性,例如:

  • 如果编译器为您提供了指向变量或函数的指针,则可以自由地对其进行取消引用(只要同时未销毁/取消分配该变量),并且是否需要预先恢复某个特定的CPU段寄存器等问题就成为了编译器的问题。使用了不同的机器代码指令

  • 如果您获得了指向数组中某个元素的指针,则可以使用指针算术将其移动到数组中的其他任何地方,甚至可以形成一个数组的最后一个地址,该地址与其他指向元素的指针进行比较是合法的在数组中(或类似地已通过指针算术移动到相同的最后一值);同样在C和C ++中,由编译器来确保此“正常工作”

  • 特定的OS功能(例如共享内存映射)可能会为您提供指针,并且它们将在对其有意义的地址范围内“正常工作”

  • 尝试将合法指针移出这些边界,或将任意数字强制转换为指针,或使用强制转换为不相关类型的指针,通常行为均未定义,因此应避免在更高级别的库和应用程序中使用,但应使用适用于OS,设备驱动程序等的代码可能需要依赖C或C ++标准未定义的行为,但仍由其特定的实现或硬件很好地定义。


p[1] *(p + 1) 相同吗?即,是否p[1] *(p + 1)生成相同的指令?
Pacerier

2
@Pacerier:来自N1570 C标准草案的6.5.2.1/2(我在网上首先找到)“下标运算符[]的定义是E1 [E2]等于(*(((E1)+(E2))) )。” -我无法想象为什么编译器不会在编译的早期阶段立即将它们转换为相同的表示形式,之后再应用相同的优化,但是我看不出有人会如何肯定地证明代码是相同的无需调查所有曾经编写的编译器。
Tony Delroy

3
@Honey:值1000十六进制太大,无法在一个字节(8位)的内存中进行编码:您只能在一个字节中存储0到255之间的无符号数字。因此,您不能在地址2000处仅存储1000个十六进制。相反,一个32位系统将使用32位(即四个字节),地址范围为2000到2003。64位系统将使用64位。位-8字节-从2000到2007。无论哪种方式,的基址p都只有2000:如果您有另一个指向p它的指针,则必须在其4或8字节中存储2000。希望有帮助!干杯。
托尼·德罗伊

1
@TonyDelroy:如果一个联合u包含一个数组arr,则gcc和clang都将认识到左值u.arr[i]可能会访问与其他联合成员相同的存储,但不会意识到左值*(u.arr+i)可能会这样做。我不确定这些编译器的作者是否认为后者会调用UB,还是前者会调用UB,但是无论如何他们都应该对其进行有用的处理,但是他们显然认为这两个表达式是不同的。
超级猫

3
我很少如此简洁明了地看到指针及其在C / C ++中的使用。
kayleeFrye_onDeck

102

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

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.

15
指针并不指向一个,它指向一个对象
基思·汤普森

51
@KeithThompson指针不指向对象,而是指向对象(也许是原语)所在的内存地址。
mg30rg 2014年

4
@ mg30rg:我不确定您要区分什么。指针值一个地址。根据定义,对象是“执行环境中的数据存储区域,其内容可以表示值”。您所说的“原始”是什么意思?C标准不使用该术语。
基思·汤普森

6
@KeithThompson我只是在指出,您实际上并没有为答案增加价值,您只是在挑剔术语(也犯了错误)。指针值肯定是一个地址,这就是它“指向”内存地址的方式。在我们的OOP驱动世界中,“对象”一词可能会引起误解,因为它可以解释为“类实例”(是的,我不知道问题被标记为[C]而不是[C ++]),因此我使用了该词。 “原始”,与“ copmlex”(数据结构,如struct或class)相反。
mg30rg 2014年

3
让我补充一下这个答案:数组下标运算符[]还取消引用了指针(a[b]定义为表示*(a + b))。
cmaster-恢复莫妮卡

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

如果没有那本书,那么图书馆员就会大喊大叫,关闭图书馆,然后准备好几个人调查一个人找到不在那本书的原因。



7

指针基础的代码和说明:

取消引用操作从指针开始,并沿其箭头移至其指针。目标可能是查看指针状态或更改指针状态。指针上的取消引用操作仅在指针具有指针的情况下才起作用-必须分配指针,并且必须将指针设置为指向指针。指针代码中最常见的错误是忘记设置指针。由于代码中的错误而导致的最常见的运行时崩溃是解除引用操作失败。在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   
}

实际上,您必须为x应该指向的位置分配内存。您的示例具有未定义的行为。
Peyman

3

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

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

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

char *p = NULL;
*p;

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

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

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

简而言之:取消引用指针意味着对其应用取消引用运算符。该运算符仅返回一个L值供您将来使用。


好吧,您取消引用了NULL指针,这将导致分段错误。
arjun gaur

最重要的是,您搜索的是“解引用运算符”而不是“解引用指针”,这实际上意味着获取值/在指针指向的内存位置访问值。
arjun gaur

你有没有尝试过?是的 以下不会崩溃:`#include <stdlib.h> int main(){char * p = NULL; * p; 返回0; }`
stsp

1
@stsp这样做是因为代码现在不会崩溃并不意味着它不会在将来或在其他系统上使用。

1
*p;导致不确定的行为。虽然你是正确的,取消引用不访问值本身,代码*p; 访问值。
MM
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.