Ton*_*roy 687

查看基本术语

通常足够好 - 除非你是编程程序集 - 设想一个包含数字内存地址的指针,1表示进程内存中的第二个字节,2表示第三个,3表示第四个等等....

  • 0和第一个字节发生了什么变化?好吧,我们稍后会讨论 - 请参阅下面的空指针.
  • 有关指针存储内容以及内存和地址关系的更准确定义,请参阅"有关内存地址的更多信息,以及您可能不需要了解的内容".

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

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

指针场景

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

const char* p = "abc";
Run Code Online (Sandbox Code Playgroud)

...用于编码字母'a','b','c'的数字值的四个字节,以及用于表示文本数据结尾的0字节,存储在内存中的某处以及数字地址数据存储在p.

例如,如果字符串文字恰好位于地址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
Run Code Online (Sandbox Code Playgroud)

请注意,地址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]
Run Code Online (Sandbox Code Playgroud)

您还可以移动指向数据的指针,随时取消引用它们:

++p;  // Increment p so it's now 0x1001
assert(*p == 'b');  // p == 0x1001 which is where the 'b' is...
Run Code Online (Sandbox Code Playgroud)

如果您有一些可以写入的数据,那么您可以执行以下操作:

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
Run Code Online (Sandbox Code Playgroud)

在上面,你必须在编译时知道你需要一个被调用的变量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_
Run Code Online (Sandbox Code Playgroud)

多字节数据类型

要使用指针,计算机程序还需要深入了解所指向的数据类型 - 如果该数据类型需要多个字节来表示,则指针通常指向数据中编号最小的字节.

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

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]
Run Code Online (Sandbox Code Playgroud)

指针动态分配内存

有时你不知道在你的程序运行之前你需要多少内存,并且看到它抛出了什么数据...然后你可以使用动态分配内存malloc.通常的做法是将地址存储在指针中......

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
Run Code Online (Sandbox Code Playgroud)

在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;
Run Code Online (Sandbox Code Playgroud)

另请参见下面的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);
Run Code Online (Sandbox Code Playgroud)

...或小心地协调任何变化的逆转......

const size_t n = ...;
p += n;
...
p -= n;  // Restore earlier value...
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

......以及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
Run Code Online (Sandbox Code Playgroud)

空指针

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

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
Run Code Online (Sandbox Code Playgroud)

在C和C++,就像内置数值类型不一定默认0,也没有boolsfalse,指针并不总是设置为NULL.所有这些都设置为0/false/NULL,当它们是static变量或(仅限C++)静态对象或其基础的直接或间接成员变量时,或者进行零初始化(例如new T();,new T(x, y, z);对T的成员执行零初始化,包括指针,而new T;才不是).

此外,当你分配0,NULLnullptr以指针的指针位不一定全部复位:指针可能不包含"0",在硬件层面,还是在你的虚拟地址空间是指到地址0.编译器允许存储别的东西存在,如果有理由,但无论它-如果你来了,比较指针0,NULL,nullptr或者被分配任何那些另一个指针,如预期的比较必须工作.因此,在编译器级别的源代码下面,"NULL"在C和C++语言中可能有点"神奇"......

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

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

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

例如,int*正确初始化以指向int变量可能 - 在转换为 - 之后float*- 访问"GPU"内存中与int变量完全不同的值,然后一旦转换为函数指针可能引用保存机器操作码的不同内存功能.

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

  • 如果编译器为您提供了一个指向变量或函数的指针,您可以自由地取消引用它(只要该变量没有被解析/解除分配),并且编译器的问题是,例如某个特定的CPU寄存器是否需要事先恢复,或者是一个独特的使用的机器代码指令

  • 如果你得到一个指向数组中元素的指针,你可以使用指针算法移动数组中的任何其他位置,甚至可以形成一个数组的地址,这个数据与其他指向元素的指针相比是合法的在数组中(或类似地通过指针算术移动到相同的一个过去的结束值); 再次在C和C++中,由编译器来确保这"正常工作"

  • 特定的操作系统功能,例如共享内存映射,可以为您提供指针,它们将"只在正常工作"的地址范围内对它们有意义

  • 试图将法律指针移到这些边界之外,或者将任意数字转换为指针,或者使用指向不相关类型的指针,通常具有未定义的行为,因此应该在更高级别的库和应用程序中避免,但是应该为操作系统,设备驱动程序等编写代码.可能需要依赖C或C++未定义的行为,但这些行为仍由其特定硬件定义.

  • @Honey:1000十六进制值太大,无法在单个字节(8位)的内存中进行编码:您只能在一个字节中存储0到255的无符号数.所以,你只是不能在"只"地址2000处存储1000个十六进制.相反,32位系统将使用32位 - 这是4个字节 - 地址从2000到2003. 64位系统将使用64位比特--8个字节 - 从2000年到2007年.无论哪种方式,`p`的基地址只有2000:如果你有另一个指向`p`的指针,它必须在其四或八个字节中存储2000.希望有所帮助!干杯. (3认同)
  • 我很少这么简洁,简单地解释过指针及其在C / C ++中的使用。 (3认同)
  • @Pacerier:从N1570草案C标准中的6.5.2.1/2开始(首先我在网上找到)"下标运算符[]的定义是E1 [E2]与(*((E1)+(E2))相同)". - 我无法想象编译器在编译的早期阶段不会立即将它们转换为相同的表示形式的任何原因,之后应用相同的优化,但我不知道任何人都可以肯定地证明代码是相同的没有调查每个编写过的编译器. (2认同)

Mah*_*esh 93

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

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.
Run Code Online (Sandbox Code Playgroud)

  • @KeithThompson指针不指向一个对象,它指向一个内存地址,一个对象(可能是一个基元)所在的位置. (44认同)
  • 指针不指向*值*,它指向*对象*. (13认同)
  • @KeithThompson我几乎没有指出,你实际上并没有给答案增加价值,你只是在挑剔术语(也做错了).指针值肯定是一个地址,就是它如何"指向"内存地址.我们的OOPdriven世界中的"对象"这个词可能会产生误导,因为它可以被解释为"类实例"(是的,我不知道这个问题被标记为[C]而不是[C++]),我使用了这个词"原始"与"copmlex"相反(数据结构类似于结构或类). (5认同)
  • @ mg30rg:我不确定你在做什么.指针值*是*地址.根据定义,对象是"执行环境中的数据存储区域,其内容可以表示值".那么"原始"是什么意思?C标准不使用该术语. (3认同)
  • 让我添加这个答案,数组下标运算符`[]`也取消引用一个指针(`a [b]`定义为'*(a + b)`). (3认同)
  • @mg30rg:我坚持我原来的说法:指针不指向一个值,它指向一个对象。并且更新了问题以纠正该问题。术语很重要,不,我不相信我有什么问题。顺便说一句,C++ 标准对“对象”一词的定义与 C 非常相似,与 OOP 无关。“原始”,听起来像是标准所说的“标量”;是的,指针指向的对象可能是也可能不是标量类型,但我不确定是否添加了任何内容。 (2认同)

bob*_*obo 16

指针是值的"引用"..很像库函数是对书的引用."解除引用"电话号码实际上正在通过并检索该书.

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.. 
Run Code Online (Sandbox Code Playgroud)

如果这本书不在那里,图书管理员就会开始大喊大叫,关闭图书馆,并且有几个人会调查一个人去寻找一本不存在的书的原因.


Fah*_*eem 13

简单来说,解除引用意味着从该指针指向的某个内存位置访问该值.


atp*_*atp 7

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   
}
Run Code Online (Sandbox Code Playgroud)