【问题标题】:What are the use pointer variables?什么是使用指针变量?
【发布时间】:2012-04-21 16:44:00
【问题描述】:

我最近尝试真正掌握 C++ 中的引用和指针,但我有点困惑。我了解*& 运算符,它们可以分别获取一个地址的值并获取一个值的地址,但是为什么不能简单地将它们与ints 等基本类型一起使用?

我不明白为什么你不能,例如,做如下的事情而不使用任何奇怪的指针变量创建:

string x = "Hello";
int y = &x; //Set 'y' to the memory address of 'x'
cout << *y; //Output the value at the address 'y' (which is the memory address of 'x')

理论上,我认为上面的代码应该输出“x”的值。 'y' 包含'x' 的内存地址,因此'*y' 应该是'x'。如果这可行(顺便说一下,在尝试编译它时,它不会——它告诉我它不能从字符串转换为 int,这没有多大意义,因为你认为内存地址可能是存储在 int 中)。

为什么我们需要使用特殊的指针变量声明(例如string *y = &amp;x)? 在这里面,如果我们在上面一行的例子中的指针声明中使用*运算符,我们将'y'的值设置为'x'的内存地址,但是稍后当我们想要访问内存地址 ('&x') 处的值,我们可以使用之前设置为内存地址的相同 '*y'。

【问题讨论】:

  • 您不能取消引用 int 变量,因此未定义 *y
  • 如果您认为内存地址应该可以存储在int 中会很有趣 - 鉴于在某些 64 位平台(具有 64 位地址空间)上,int定义为 32 位整数。
  • @Damien_The_Unbeliever:然后将问题改写为“为什么不在任何地方使用intptr_t?”或“为什么不到处使用void*?”。他想知道为什么所有不同的指针类型都是必要的。
  • @BenVoigt - 我认为这里有两个独立的概念 - 1)为什么指针应该被认为与整数不同,以及 2)为什么指向不同类型的指针不同。这两个想法/概念都值得探索。
  • @Damien:void*intptr_t 没有太多理由分开。你陷入了大小的问题,但是有指针大小的整数,所以这更多的是为什么intintptr_t的大小不同,与指针或这个问题无关。

标签: c++ pointers reference


【解决方案1】:

C 和 C++ 在编译时而非运行时解析类型信息。甚至运行时多态性也依赖于编译器构造一个函数指针表,该表在编译时固定偏移量。

因此,程序可以知道cout &lt;&lt; *y; 正在打印字符串的唯一方法是因为y 被强类型化为指向字符串的指针(std::string*)。程序不能仅从地址确定存储在地址y 的对象是std::string。 (即使 C++ RTTI 也不允许这样做,您需要足够的类型信息来识别多态基类。)

【讨论】:

  • 所以这个想法是指针只是处理整个“看起来,一个内存地址,它开始我们现在不知道的某种数据结构的内存地址”的好方法!有问题吗?
  • @Joesavage1:你最好先知道数据的结构(类型)是什么,因为你无法从数据中找出来。在运行时,指针只包含一个地址,但在编译时,指针类型的变量也有信息告诉编译器它指向什么类型的数据。这就是cout &lt;&lt; *y; 知道打印字符串(而不是数字等)的方式,因为ystd::string*。如果您查看 HostileFork 的答案,要将整数从整数转换回指针, 必须告诉编译器所指向的内容。该信息不会与对象一起保存。
【解决方案2】:

简而言之,C 是一种类型化语言。你不能在变量中存储任意的东西。

查看维基百科上的type safety 文章。 C/C++ 通过检查操作数和函数参数的类型来防止在编译时出现问题的操作和函数调用(但请注意,通过显式转换,您可以更改表达式的类型)。

将字符串存储在整数中没有意义 -> 就像在其中存储指针没有意义一样。

【讨论】:

  • 除非您可以将指针存储在整数中(使用强制转换)。当您这样做时,它只会丢失其类型信息。
  • 嗯.. 我会说你可以 cast 一个指向整数的指针(丢失类型),然后 then 将它存储在一个指针中(但是你已经在存储一个指针..)
【解决方案3】:

简单来说,内存地址有一个类型,就是指针。指针不是整数,因此不能将指针存储在 int 变量中。如果你好奇为什么整数和指针不可替代,那是因为它们的大小是由实现定义的(有一定的限制),并且不能保证它们的大小相同。

例如,正如@Damien_The_Unbeliever 指出的那样,64 位系统上的指针必须是 64 位长,但 int 是 32 位是完全合法的,只要它不超过 long 并且也不短于短片。

至于为什么每种数据类型都有自己的指针类型,那是因为每种类型(尤其是用户定义的类型)在内存中的结构不同。如果我们要取消引用无类型(或void)指针,将没有信息表明应该如何解释该数据。另一方面,如果您要创建一个通用指针并消除指定类型的“不便”,那么内存中的每个实体可能都必须与其类型信息一起存储。虽然这是可行的,但它远非高效,而效率是 C++ 的设计目标。

【讨论】:

  • 这并不能解释为什么std::string* 是与intptr_t 不同的类型。尺寸只是很小的一部分原因。
  • @BenVoigt 做了一些补充,希望能解决剩下的问题。
  • 所以本质上指针本身并不是必需的,因为理论上这些技术可以通过其他方法完成,但是它创造了一种非常好的方式来完成所有事情,包括错误检测、架构调整、读取正确的数字内存中的位,知道正在读取的东西是什么类型(类型)等等?
【解决方案4】:

一些非常低级的语言...例如机器语言...完全按照您的描述运行。一个数字就是一个数字,程序员可以在脑海中记住它所代表的含义。一般来说,高级语言的希望是让您远离这种开发风格带来的担忧和潜在的错误。

您实际上可以无视 C++ 的类型安全,但后果自负。例如,我拥有的 32 位机器上的 gcc 在我运行时会打印“Hello”:

string x = "Hello";
int y = reinterpret_cast<int>(&x);
cout << *reinterpret_cast<string*>(y) << endl;

但正如几乎所有其他回答者所指出的那样,不能保证它可以在另一台计算机上运行。如果我在 64 位机器上尝试这个,我会得到:

错误:从“std::string*”转换为“int”会丢失精度

我可以通过将其更改为 long 来解决这个问题:

string x = "Hello";
long y = reinterpret_cast<long>(&x);
cout << *reinterpret_cast<string*>(y) << endl;

C++ 标准为这些类型指定了最小值,但没有指定最大值,所以当你面对一个新的编译器时,你真的不知道你将要处理什么。见:What does the C++ standard state the size of int, long type to be?

因此,一旦您开始走这条路并“抛弃”语言中的安全性,编写不可移植代码的可能性就很高。 reinterpret_cast 是最危险的选角方式……

When should static_cast, dynamic_cast, const_cast and reinterpret_cast be used?

但这只是从技术上深入研究“为什么不是 int”部分,以防您感兴趣。请注意,正如@BenVoight 在下面的评论中指出的那样,从 C99 开始确实存在一个名为 intptr_t 的整数类型,它保证可以容纳任何指针。因此,与丢失精度相比,丢弃类型信息会带来更大的问题……比如不小心转换回错误的类型!

【讨论】:

  • intptr_t,保证大小合适。真正的问题不是精度损失,而是编译时类型信息的损失。
  • @BenVoigt 当然可以。但我并没有试图让我的答案涵盖其他人在这方面所说的一切,只是展示了一些铸造,因为没有其他人有(问题是关于 int 的)。没有提到intptr_t,因为它不是一种在其他地方的程序中可能会普遍使用的类型......你会发现很少有例程会将它作为参数。将某些东西变成 intptr_t 确实有合适的容量,但会给你一个“怪胎”值,也可能是 char*
【解决方案5】:

C++ 是一种强类型语言,指针和整数是不同的类型。通过制作这些单独的类型,编译器能够检测到误用并告诉您您所做的事情是不正确的。

同时,指针类型维护了指向对象类型的信息,如果你获得了一个double的地址,你必须把它存储在double*中,编译器知道你取消引用那个指针将到达double。在您的示例代码中,int y = &amp;x; cout &lt;&lt; *y; 编译器会丢失 y 指向 的信息,表达式 *y 的类型将是未知的,并且无法确定哪个operator&lt;&lt; 的不同重载要调用。与std::string *y = &amp;x; 相比,编译器看到y 它知道它是std::string* 并且知道取消引用它会得到std::string(而不是双精度或任何其他类型),从而使编译器能够静态检查所有包含y 的表达式。

最后,当您认为指针只是对象的地址并且应该可以用整数类型表示时(在 64 位架构上必须是 int64 而不是 @987654333 @) 并非总是如此。在不同的体系结构上,指针不能真正用整数值表示。例如,在具有分段内存的体系结构中,对象的地址可以包含一个段(整数值)和该段的偏移量(另一个整数值)。在其他架构中,指针的大小不同于任何整数类型的大小。

【讨论】:

  • 两个整数仍然可以存储在一个更大的整数中;)
  • @BenVoigt:但它们不一定具有相同的含义。段可能重叠,16 位 x86 中的实际地址为 (IIRC) segment &lt;&lt; 2 + offset,其中 segmentoffset 为 16 位。即使实际地址只有 10 位,段信息也很重要,因此您必须存储整个 32 位,然后在取消引用之前将其分别加载到不同的寄存器中......并非不可能,但对于一个编译器提供一个特性,它的唯一优点是它实际上丢失了类型信息。
【解决方案6】:

该语言试图保护您不要混淆两个不同的概念 - 即使在硬件级别它们都只是一组位;

除了需要在调试器的各个部分之间手动传递值之外,您永远不需要知道数值。

在数组的古老用法之外,将“加 10”到指针是没有意义的 - 所以你不应该将它们视为数值。

通过编译器保留类型信息,它还可以防止你犯错误 - 如果所有指针都相等,那么编译器就无法指出你试图取消引用的内容因为int 是指向string 的指针。

【讨论】:

  • 如果这是原因,可能只有一种类型ptr,用于所有指针。
  • @chris - 我 am 在这里很生疏 - 但据我了解,指针算术仅适用于数组,而且据我所知,数组的使用并不多在现代 C++ 中。你能纠正我吗?
  • 如果内存都在那里,它可以“工作”任何东西,但是数组将是我能想到的最结构化的用法,是的。我想如果内存都在那里,无论如何它都必须代表一个数组。迭代器就像指针,除了它们实际上可以跟踪下一个元素的位置。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-12
  • 2015-01-14
  • 1970-01-01
  • 1970-01-01
  • 2012-11-11
相关资源
最近更新 更多