什么是“取消引用”;一个指针意味着什么?

请包括一个例子的解释。

740527 次浏览

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

解引用操作开始于 指针和它的箭头 来访问它的指针。目标可能是 来查看指向状态或 更改指针状态。的 对指针的解引用操作 只有当指针有 Pointee - 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
}

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

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.

复习基本术语

通常已经足够好了(除非您正在编写程序集),可以设想< em > < / em >指针包含一个数字内存地址,其中1指进程内存中的第二个字节,2指第三个字节,3指第四个字节,以此类推,....

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

当您想要访问指针所指向的内存中的数据/值时——具有数值索引的地址的内容——那么您可以< em >废弃< / em >指针。

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

指针场景

考虑在C语言中,给定一个指针,例如下面的p

const char* p = "abc";

...四个字节的数值用于编码字母'a', 'b', 'c',一个0字节表示文本数据的结束,存储在内存的某个地方,数据的数字地址存储在p中。这种C语言在内存中编码文本的方式被称为< em > ASCIIZ < / em >

例如,如果字符串字面值的地址恰好是0x1000,而p是一个32位指针的地址是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语言中,NULL0——以及在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++)静态对象或其基类的直接或间接成员变量,或进行零初始化时,所有这些都被设置为0/false/NULL(例如,new T();new T(x, y, z);对T的成员(包括指针)执行零初始化,而new T;则没有)。

此外,当你将0NULLnullptr赋值给一个指针时,指针中的位并不一定全部重置:指针在硬件层面上可能不包含“0”,或者指向虚拟地址空间中的地址0。如果有必要,编译器可以在那里存储其他东西,但无论它做什么——如果你来比较指针与0NULLnullptr或其他被赋值的指针,比较必须按预期工作。因此,在编译器级别的源代码以下,“NULL”在C和c++语言中可能有点“神奇”……

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

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

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

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

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

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

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

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

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

指针是一个值的“引用”。就像图书馆的电话号码是一本书的参考资料一样。“取消引用”电话号码是在物理上检查和检索这本书。

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

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

简而言之,解引用意味着从指针所指向的某个内存位置访问值。

我认为前面所有的答案都是错误的,因为他们 声明取消引用意味着访问实际值。 维基百科给出了正确的定义: # EYZ0 < / p >

它操作一个指针变量,并返回一个与指针地址上的值等价的l值。这被称为指针的“解引用”。

也就是说,我们可以不引用任何指针 访问它所指向的值。例如:< / p >
char *p = NULL;
*p;
在没有访问NULL指针的情况下解引用了它 价值。或者我们可以这样写:

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

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

.触摸实际数据 所以简而言之:取消对指针的引用意味着应用 对它的解引用操作符。运算符只是返回一个