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

mmvthczy  于 2023-04-01  发布在  C/C++
关注(0)|答案(6)|浏览(245)

请在解释中附上一个例子。

lqfhib0f

lqfhib0f1#

复习基本术语

通常情况下,除非你在编写汇编程序,否则想象一个包含数字内存地址的指针,其中1指的是进程内存中的第二个字节,2指的是第三个字节,3指的是第四个字节,依此类推。

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

当你想访问指针指向的内存中的数据/值时-具有该数字索引的地址的内容-然后你***解引用***指针。
不同的计算机语言有不同的符号来告诉编译器或解释器,你现在对指向的对象的(当前)值感兴趣-我下面重点介绍C和C++。

指针场景

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

const char* p = "abc";

...四个字节的数值用于编码字母'a','b','c',和一个0字节表示文本数据的结束,被存储在内存中的某个地方,该数据的数字地址存储在p.这种方式C在内存中编码的文本被称为ASCIIZ.
例如,如果字符串字面量恰好位于地址0x 1000,而p是一个位于0x 2000的32位指针,则内存内容将为:

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

注意,地址0x 1000没有变量名/标识符,但我们可以使用存储其地址的指针间接引用字符串字面量: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中,最好的做法是使用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)...

{
    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。(仅限C)静态对象或其基的直接或间接成员变量,或者进行零初始化(例如new T();new T(x, y, z);对T的成员(包括指针)执行零初始化,而new T;不执行)。

此外,当您将0NULLnullptr分配给指针时,指针中的位不一定全部重置:指针可能不包含硬件级别的“0”,或者引用虚拟地址空间中的地址0。如果有理由,编译器可以在那里存储其他内容,但无论它做什么-如果您将指针与0NULLnullptr或其他分配了任何这些的指针进行比较,比较必须按预期工作。所以,在编译器级别的源代码下面,“NULL”在C和 C++ 中可能有点“神奇”...

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

更严格地说,初始化指针存储一个位模式,标识NULL或(通常为virtual)内存地址。
简单的情况是,这是进程的整个虚拟地址空间的数字偏移量;在更复杂的情况下,指针可以相对于一些特定的存储器区域,CPU可以基于CPU“段”寄存器或在位模式中编码的段id的某种方式来选择该特定的存储器区域,和/或根据使用地址的机器代码指令来查找不同的位置。
例如,正确初始化为指向int变量的int*可能在转换为float*之后访问“GPU”内存中的内存,该内存与int变量所在的内存完全不同,然后一旦被转换为函数指针并用作函数指针,它可能指向保存程序的机器操作码的进一步不同的内存(int*的数值实际上是这些其他内存区域内的随机无效指针)。
像C和C++这样的3GL编程语言倾向于隐藏这种复杂性,例如:

  • 如果编译器给了你一个指向变量或函数的指针,你可以自由地取消引用它(只要变量没有同时被析构/解除分配),这是编译器的问题,例如是否需要预先恢复特定的CPU段寄存器,或者使用不同的机器代码指令。
  • 如果你得到一个指向数组中某个元素的指针,你可以使用指针算法将其移动到数组中的任何其他位置,甚至可以形成一个数组的过尾地址,该地址法律的与数组中其他指向元素的指针进行比较(或者类似地通过指针算法移动到相同的过尾值);同样在C和C++中,编译器负责确保这“正常工作”
  • 特定的操作系统功能,例如共享内存Map,可能会给予你指针,它们会“只工作”在对它们有意义的地址范围内
  • 试图将法律的指针移动到这些边界之外,或将任意数字转换为指针,或使用转换为不相关类型的指针,通常具有undefined behaviour,因此应该在更高级别的库和应用程序中避免,但操作系统,设备驱动程序等的代码可能需要依赖于C或C++标准未定义的行为,尽管如此,它们的特定实现或硬件定义了这些行为。
ncgqoxb0

ncgqoxb02#

解引用一个指针意味着获取存储在该指针所指向的内存位置的值。操作符 * 用于完成此操作,称为解引用操作符。

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

bgtovc5b3#

指针是一个值的“引用”。就像图书馆的索书号是一本书的引用一样。“取消引用”索书号是物理地遍历和检索那本书。

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

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

zwghvu4y

zwghvu4y4#

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

gwo2fgha

gwo2fgha5#

来自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   
}
myss37ts

myss37ts6#

我认为前面所有的答案都是错误的,因为他们说解引用意味着访问实际值。Wikipedia给出了正确的定义:https://en.wikipedia.org/wiki/Dereference_operator
它操作一个指针变量,并返回一个与指针地址相等的左值。这被称为“解引用”指针。
也就是说,我们可以解引用指针而不访问它所指向的值。例如:

char *p = NULL;
*p;

我们解引用NULL指针而不访问它的值。或者我们可以这样做:

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

同样,解引用,但永远不会访问值。这样的代码不会崩溃:当你用一个无效的指针实际 * 访问 * 数据时,崩溃就会发生。然而,不幸的是,根据标准,对一个无效的指针进行解引用是一种未定义的行为(有一些例外),即使你不试图接触实际的数据。
简而言之:解引用指针意味着对指针应用解引用操作符。该操作符只返回一个左值供将来使用。

相关问题