请在解释中包含一个示例。
答案
审查基本术语
它是通常 足够好 - 除非您正在编程组件 - 设想pointer包含一个数字内存地址,其中1指在过程内存中的第二个字节,第三个,第3个,第四个等等。
- 0和第一个字节发生了什么?好吧,我们稍后再解决 - 看看无效指针以下。
- 有关指针存储的更准确的定义以及记忆和地址如何相关的记忆和地址,请参见*“有关内存地址的更多信息,以及为什么您可能不需要知道”*在这个答案的结尾。
当您想在指针指向的内存中访问数据/值时 - 使用该数值索引的地址的内容 - 然后您dereference指针。
不同的计算机语言具有不同的符号,可以告诉您现在对尖头对象的(当前)值感兴趣的编译器或解释器 - 我将重点放在C和C ++上。
指针方案
在C中考虑一个指针,例如p
以下…
const char* p = "abc";
…四个字节具有用于编码字母" a"," b’,‘c’的数值的四个字节,以及代表文本数据末尾的0个字节,存储在内存中的某个地方和该字节的数值地址数据存储在p
。这样C C在内存中编码文本被称为ASCIIZ。
例如,如果字符串恰好在地址0x1000,并且p
位于0x2000的32位指针,内存内容将为:
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
,也不bools
到false
,指针并不总是设置为NULL
。所有这些设置为0/false/null时static
变量或(仅C ++)静态对象或其碱基的直接或间接成员变量,或进行零初始化(例如,new T();
和new T(x, y, z);
对T的成员(包括指针)进行零启动化,而new T;
才不是)。
此外,当您分配0
,,,,NULL
和nullptr
对于指针,指针中的位不一定全部重置:指针可能在硬件级别上不包含" 0",或者在虚拟地址空间中参考地址0。如果有理由,则允许编译器将其他内容存储在那里,但是无论它做什么 - 如果您来并比较指针0
,,,,NULL
,,,,nullptr
或另一个指向其中任何一个指针,比较必须按预期工作。因此,在编译器级别的源代码下方," null"在C和C ++语言中可能有点"魔术" …
有关内存地址的更多信息,以及为什么您可能不需要知道
更严格的是,初始化的指针存储了一个识别的位模式NULL
或一个(通常虚拟的)内存地址。
简单的情况是,这是该过程的整个虚拟地址空间中的数字偏移;在更复杂的情况下,指针可能相对于某些特定的内存区域,CPU可以根据CPU"段"寄存器或某种方式在Bit-Pattern中编码的段ID和/或在不同的位置进行选择。使用地址的机器代码说明。
例如,int*
适当初始化以指向int
变量可能 - 铸造到float*
- " gpu"内存中的访问内存与内存完全不同int
变量是,然后将其用作函数指针并将其用作函数指针,它可能会指向该程序的进一步的不同存储器固定机opcodes(带有的数字值int*
在这些其他内存区域内有效地是随机的,无效的指针)。
3GL编程语言(例如C和C ++)倾向于隐藏此复杂性,因此:
如果编译器为您提供了变量或功能的指针,则可以自由放置(只要变量没有破坏/交易,同时),并且是否是编译器的问题。需要事先恢复特定的CPU段寄存器或使用的不同机器代码指令
如果您在数组中获取指向元素的指针,则可以使用指针算术在数组中的其他任何地方移动,甚至可以形成与其他指针与其他指针进行比较的阵列的地址一正式的末端在阵列中(或通过指针算术转移到相同的一式末端值的阵列中);同样,在C和C ++中,由编译器确保此"只有"
特定的OS功能,例如共享的内存映射,可能会给您指示,它们将在对他们有意义的地址范围内"工作"
试图将法律指针移至这些界限之外,或将任意数字施加给指针,或者使用铸造的指针到无关的类型,通常是不确定的行为,因此应该在更高级别的库和应用程序中避免,但是OS,设备驱动程序等的代码可能需要依靠C或C ++标准的不确定的行为,但是,这是由其特定的实现或硬件所定义的。