在 C/C++ 中,“取消引用”指针是什么意思?

What does 'dereferencing' a pointer mean in C/C++?

提问人:asir 提问时间:2/10/2011 最后编辑:user16217248asir 更新时间:3/30/2023 访问量:786083

问:

请附上一个例子和解释。

C C 指针 取消引用 C++-FAQ

评论

26赞 Erik 2/10/2011
cslibrary.stanford.edu/106
32赞 Peyman 2/10/2011
int *p;将定义一个指向整数的指针,并将取消引用该指针,这意味着它实际上将检索 p 指向的数据。*p
5赞 templatetypedef 2/10/2011
Binky's Pointer Fun (cslibrary.stanford.edu/104) 是一个很棒的视频,关于可以澄清事情的指针。@Erik- 你为建立斯坦福 CS 图书馆链接而摇滚。那里有很多好东西......
7赞 Jim Balter 2/10/2011
哈利的回答与这里的帮助相反。
1赞 M.M 7/3/2018
@Peyman 不检索 p 指向的数据。相反,它指定内存位置。然后,该表达式可以继续用于存储新数据,或从中检索数据,或者什么都不用。*p

答:

8赞 rxmnnxfpvg 2/10/2011 #1

指针基础中的代码和说明:

取消引用操作从 指针并跟随其箭头 以访问其指针。目标可能是 查看指针状态或 更改指针状态。这 指针上的取消引用操作 仅当指针具有 pointee -- 指针必须是 已分配,并且必须设置指针 指向它。最常见的错误 在指针代码中忘记设置 向上指尖。最常见的 运行时崩溃,因为该错误 代码是失败的取消引用 操作。在 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   
}

评论

0赞 Peyman 2/10/2011
实际上,您必须为 x 应该指向的位置分配内存。您的示例具有未定义的行为。
134赞 Mahesh 2/10/2011 #2

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

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.

评论

16赞 Keith Thompson 4/30/2013
指针不指向,而是指向对象
71赞 mg30rg 3/6/2014
@KeithThompson 指针不指向对象,而是指向对象(可能是基元)所在的内存地址。
5赞 Keith Thompson 3/7/2014
@mg30rg:我不确定你在做什么区分。指针值是一个地址。根据定义,对象是“执行环境中的数据存储区域,其内容可以表示值”。你说的“原始”是什么意思?C 标准不使用该术语。
11赞 mg30rg 3/7/2014
@KeithThompson我几乎没有指出,你实际上并没有为答案增加价值,你只是在术语上吹毛求疵(而且也做错了)。指针值肯定是一个地址,这就是它“指向”内存地址的方式。在我们的 OOP 驱动的世界中,“对象”这个词可能会产生误导,因为它可以解释为“类实例”(是的,我不知道这个问题被标记为 [C] 而不是 [C++]),我使用了“原始”这个词,因为与“copmlex”(像结构或类这样的数据结构)相反。
4赞 cmaster - reinstate monica 6/16/2014
让我在这个答案中补充一点,数组下标运算符也取消了对指针的引用(定义为 表示)。[]a[b]*(a + b)
889赞 26 revs, 9 users 81%Tony Delroy #3

查看基本术语

除非你正在对汇编进行编程,否则通常设想一个包含数字内存地址的指针足够了,其中 1 表示进程内存中的第二个字节,2 表示第三个字节,3 表示第四个字节,依此类推。

  • 0 和第一个字节发生了什么?好吧,我们稍后会谈到这一点 - 请参阅下面的 null 指针
  • 有关指针存储的内容以及内存和地址之间的关系的更准确定义,请参阅本答案末尾的“有关内存地址的详细信息,以及您可能不需要知道的原因”。

当您想要访问指针指向的内存中的数据/值(具有该数字索引的地址内容)时,您可以取消引用指针。

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

指针方案

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

const char* p = "abc";

...四个字节的数值用于对字母“a”、“b”、“c”进行编码,以及一个 0 字节(表示文本数据的末尾)存储在内存中的某个位置,该数据的数字地址存储在 中。这种方式 C 对内存中的文本进行编码称为 ASCIIZp

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

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

取消引用指针

为了引用指向的字符,我们使用以下符号之一取消引用(同样,对于 C):pp

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++ 中,内存分配通常使用运算符完成,而释放则使用:newdelete

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 和 - 此外在 C++ 中 - 可用于指示指针当前不保存变量的内存地址,并且不应取消引用或在指针算术中使用。例如:NULL0nullptr

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++ 中,正如内置数值类型不一定默认为 ,也不一定默认为 ,指针并不总是设置为 。当它们是变量或(仅限 C++)静态对象或其基的直接或间接成员变量时,所有这些都设置为 0/false/NULL,或者进行零初始化(例如 并对 T 的成员(包括指针)执行零初始化,而不执行)。0boolsfalseNULLstaticnew T();new T(x, y, z);new T;

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

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

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

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

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

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

  • 如果编译器给你一个指向变量或函数的指针,你可以自由地取消引用它(只要变量没有被同时解构/释放),这是编译器的问题,例如,是否需要事先恢复特定的 CPU 段寄存器,或者使用不同的机器代码指令

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

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

  • 尝试将合法指针移动到这些边界之外,或将任意数字转换为指针,或使用转换为不相关类型的指针,通常具有未定义的行为,因此应避免在更高级别的库和应用程序中使用,但操作系统、设备驱动程序等的代码可能需要依赖于 C 或 C++ 标准未定义的行为, 然而,这由它们的具体实现或硬件很好地定义。

评论

3赞 Tony Delroy 9/23/2013
@Pacerier:从 N6.5.2.1 C 标准草案中的 2/1570(我第一次在网上找到)“下标运算符 [] 的定义是 E1[E2] 等同于 (*((E1)+(E2)))。” - 我无法想象编译器为什么不会在编译的早期阶段立即将它们转换为相同的表示形式,然后应用相同的优化,但我看不出任何人在不调查每个编译器的情况下如何明确证明代码是相同的。
3赞 Tony Delroy 7/13/2017
@Honey:值 1000 十六进制太大,无法在单个字节(8 位)内存中编码:您只能在一个字节中存储 0 到 255 的无符号数字。因此,您不能在“仅”地址 2000 处存储 1000 个十六进制。相反,32 位系统将使用 32 位(即 4 个字节),地址从 2000 年到 2003 年。从 2000 年到 2007 年,64 位系统将使用 64 位 - 8 个字节。无论哪种方式,它的基址都只是 2000:如果您有另一个指向它的指针,则必须将 2000 存储在其 4 个或 8 个字节中。希望对您有所帮助!干杯。pp
1赞 supercat 6/30/2018
@TonyDelroy:如果联合包含数组,则 gcc 和 clang 都会识别出左值可能与其他联合成员访问相同的存储,但不会识别左值可能会这样做。我不确定这些编译器的作者是否认为后者调用了 UB,或者前者调用了 UB,但他们无论如何都应该对其进行有用的处理,但他们显然认为这两种表达式是不同的。uarru.arr[i]*(u.arr+i)
7赞 kayleeFrye_onDeck 7/10/2018
我很少看到指针及其在 C/C++ 中的使用如此简洁和简单的解释。
1赞 supercat 7/10/2018
@TonyDelroy:安全和优化需要的不是“位转换”运算符,而是“受限指针”类型,在其生命周期内,它要求使用受限指针访问的对象的所有部分都只能通过它进行访问,并且其构造函数可以采用任何类型的指针,并导致通过受限指针进行的访问被视为对原始类型的访问。大多数需要使用类型双关语的代码都可以接受这种构造,并且它将允许许多超出 TBAA 的有用优化。
23赞 bobobobo 12/11/2013 #4

指针是对值的“引用”。就像图书馆的索书号是对一本书的引用一样。“取消引用”呼叫号码是物理检查和检索该书。

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

如果书不在那里,图书管理员就会开始大喊大叫,关闭图书馆,然后几个人开始调查一个人找到一本不在那里的书的原因。

23赞 Fahad Naeem 12/30/2013 #5

简单来说,取消引用意味着从该指针所指向的某个内存位置访问该值。

3赞 stsp 4/16/2016 #6

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

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

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

char *p = NULL;
*p;

我们取消了对 NULL 指针的引用,但未访问其 价值。或者我们可以做:

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

同样,取消引用,但从不访问该值。这样的代码不会崩溃: 当您通过以下方式实际访问数据时,就会发生崩溃 无效的指针。然而,不幸的是,根据 标准,取消引用无效指针是未定义的 行为(除了少数例外),即使你不尝试 触摸实际数据。

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

评论

0赞 arjun gaur 9/2/2016
好吧,您取消了引用 NULL 指针,这将导致分段错误。
0赞 arjun gaur 9/2/2016
最重要的是,您搜索的是“取消引用运算符”而不是“取消引用指针”,这实际上意味着在指针指向的内存位置获取值/访问值。
0赞 stsp 9/3/2016
你试过吗?我做了。以下内容不会崩溃: ' #include < stdlib.h> int main() { char *p = NULL; *p; return 0; } '
1赞 9/10/2017
@stsp 因为代码现在不会崩溃,并不意味着它将来不会崩溃,或者在其他系统上不会崩溃。
1赞 M.M 7/3/2018
*p;导致未定义的行为。尽管您认为取消引用本身不会访问该值,但代码确实会访问该值,这是对的。*p;