【问题标题】:Why don't some languages allow declaration of pointers?为什么有些语言不允许声明指针?
【发布时间】:2010-11-02 04:34:51
【问题描述】:

我现在正在使用 C++ 编程,我喜欢使用指针。但似乎其他较新的语言(如 Java、C# 和 Python)不允许您显式声明指针。换句话说,您不能同时编写int xint * y,并让x 是一个值,而y 是一个指针,在任何这些语言中。这背后的原因是什么?

【问题讨论】:

  • 大多数人会说它们可能很危险,因为它们能够搞砸并造成段错误和/或内存泄漏。
  • 你用你喜欢的指针做什么?
  • @GMan--我认为我最喜欢的事情是允许一个类A 包含指向另一个类B 的指针,这样如果B 的对象实际上是一个类C继承自B,则A 可以调用C 的函数,而不是B 的虚函数。我想其他语言可能会以不同的方式实现以避免对指针的需求。此外,您可以使两个类包含彼此的指针,以便两者都可以访问每个函数,但您不能对值执行此操作,因为它会创建一个无限循环,其中类 A 包含类 B 包含类 @987654336 @...
  • 指针本身还不错..它们被使用很糟糕..仅此而已:)
  • 嗯,false premise.

标签: c++ pointers


【解决方案1】:

指针还不错,只是很容易出错。在较新的语言中,他们已经找到了做同样事情的方法,但射中自己脚的风险较小。

虽然指针没有错。去爱他们吧。

对于您的示例,您为什么希望 x 和 y 都指向同一个内存?为什么不总是称它为 x?

还有一点,指针意味着您必须自己管理内存生命周期。较新的语言更喜欢使用垃圾收集来管理内存,并且允许使用指针会使这项任务变得非常困难。

【讨论】:

  • 好吧,我并不是说xy 应该永远指向同一个内存地址,只是你不能将x 声明为存储值,而y作为存储内存地址。不过我不知道,我更喜欢能够始终明确地在使用值或引用声明变量之间进行选择。
  • 当然,没有什么好东西是免费的。 C++ 指针是强大而危险的,而在其他一些语言中,虽然你不能在脚下开枪,但你根本很难瞄准。从高级语言如何处理可应用于 C++ 的指针中可以学到一些东西。
  • “指针意味着你必须自己管理内存生命周期”这是关于 C++ 的最普遍和令人沮丧的误解之一。您可以手动管理动态分配的对象生命周期,但您没有必须(大多数时候您绝对不应该这样做)。智能指针和容器等设施可用于确保自动生命周期管理。仅考虑生命周期管理,这种方法实际上比垃圾回收更好,因为生命周期也是确定性的。
  • @Steve:那么当你使用 GC 时,它仍然是 you 管理内存,因为 you 是确保你使用 GC 语言的人?
  • @GMan:即使在 GC 语言中,要摆脱子对象,您也必须将父对象中的指针设置为 null。正是由于这个原因,有很多框架(尤其是 GUI 框架)容易泄漏。在框架的某个地方,一个指针仍然存在,并且一整套相互连接的对象被泄露。
【解决方案2】:

I'll start with one of my favorite Scott Meyers quotes:

当我就异常处理进行演讲时,我教给人们两件事:

  • 指针是你的敌人,因为它们会导致 auto_ptr 旨在消除的各种问题。

  • 指针是你的朋友,因为指针上的操作不能抛出。

然后我告诉他们有一个美好的一天:-)


关键是指针非常有用,在使用 C++ 编程时理解它们当然是必要的。不理解指针就无法理解 C++ 内存模型。在实现资源拥有类(例如智能指针)时,您需要使用指针,并且可以利用它们的不抛出保证来编写异常安全的资源拥有类。

但是,在编写良好的 C++ 应用程序代码中,您永远不必使用原始指针。绝不。您应该始终使用一些抽象层,而不是直接使用指针:

  • 尽可能使用引用而不是指针。引用不能为空,它们使代码更易于理解、更易于编写和更易于代码审查。

  • 使用智能指针来管理您使用的任何指针。 shared_ptrauto_ptrunique_ptr 等智能指针有助于确保您不会泄露资源或过早释放资源。

  • 使用标准库中的容器来存储对象集合,而不是自己分配数组。通过使用vectormap 之类的容器,您可以确保您的代码是异常安全的(这意味着即使抛出异常,您也不会泄漏资源)。

  • 在使用容器时使用迭代器。正确使用迭代器比正确使用指针要容易得多,而且许多库实现都提供调试支持以帮助您找到错误使用它们的位置。

  • 当您使用旧版或第三方 API 并且绝对必须使用原始指针时,请编写一个类来封装该 API 的使用。

C++ 以 Scope-Bound Resource Management(SBRM,也称为 Resource Acquisition is Initialization,或 RAII)的形式具有自动资源管理。用它。如果你不使用它,你就做错了。

【讨论】:

  • 引用可以为“null”,例如,当您引用取消引用的无效指针时。见:codepad.org/JEPQpv2v
  • @Paul:您的代码 sn-p 的行为未定义。 “空引用不能存在于定义良好的程序中,因为创建此类引用的唯一方法是将其绑定到通过取消引用空指针获得的“对象”,这会导致未定义的行为”(C++03 8.3 .2/4).
  • @James McNellis:访问空指针也是未定义的行为,但这不是重点。关键是,完全有可能有一个“无效”的引用(即“空”/未定义行为的黑洞/等)。可能会达到一点点,但是遇到的时候调试一下肯定很有趣……
  • @Paul:嗯,有很多事情会导致未定义的行为。当您编写一个带有引用的函数时(例如您的示例中的f()),您可以正确地假设该引用是有效的,因为如果不是,您的调用者已经搞砸了程序。是的,您可能会遇到与此问题相关的错误,但通常它们之间相差甚远(根据我的经验,它们通常足够明显以至于它们会在代码审查中被发现)。
  • @Paul:错了。在 C++ 中,您如何得出引用无效的结论?你不能,因为在一个定义明确的程序中,你永远不会有一个错误的引用。当你说有可能时,你打败了自己,因为你不能再可靠地观察你的程序的状态,这是判断引用无效所必需的。 (换句话说,问题在于调用 UB 的人,而不是引用。引用不能为空;可以输入 UB。)
【解决方案3】:

指针可能会被滥用,托管语言更愿意保护您免受潜在陷阱的影响。但是,指针当然也不错——它们是 C 和 C++ 语言的一个组成部分,没有它们编写 C/C++ 代码既棘手又麻烦。

【讨论】:

    【解决方案4】:

    真正的“指针”有两个特点。

    • 它保存另一个对象(或原语)的地址
      • 并公开该地址的数字性质,以便您进行算术运算。

    通常为指针定义的算术运算是:

    1. 将整数添加到数组中的指针中,该数组返回另一个元素的地址。
    2. 将两个指针相减到同一个数组中,返回中间元素的数量(包括一端)。
    3. 比较两个指针到同一个数组中,这表明哪个元素更接近数组的头部。

    托管语言通常会引导您走“引用”而不是指针的道路。引用也包含另一个对象(或原语)的地址,但不允许进行算术运算。

    除其他外,这意味着您不能使用指针算术来离开数组的末尾并使用错误的类型处理其他一些数据。另一种形成无效指针的方法是在此类环境中使用垃圾收集来处理。

    这共同确保了类型安全,但会严重失去通用性。

    【讨论】:

    • 我不明白你是如何失去普遍性的。没有指针只会迫使您直言不讳:如果您想要一个项目数组,则将其声明为一个项目数组并使用整数索引,可以进行数字操作。 (请注意,在 C 和 C++ 中,仅在指向同一内存块(即数组)的指针之间正式允许指针运算。
    • @zvrba:托管语言不允许您形成对子对象的引用这一事实是对一般性的巨大损失。无法将双关语键入 char* 是对一般性的巨大损失。另外,我的回答强调两个指针都需要位于同一个块中。尽管如此,指向不同对象的指针具有未定义但稳定的顺序,在托管环境中不再做出这种保证,垃圾收集器不仅可以移动对象,还可以更改它们的顺序。
    • “失去一般性”是什么意思?并且不安全的强制转换(例如,到 char*)不是指针的固有属性,它只是一个 C 特性。
    • @zvrba:正如我所说,指针暴露了底层地址的数字性质。将指针转换为整数和将整数转换为指针的能力足以设置类型双关(您称之为不安全强制转换,但类型双关的许多用途实际上是完全安全的)。
    • 呃,C 也可以在具有非线性内存模型的机器上运行。在那里,转换为整数并返回没有多大意义。指针是可以用来到达另一个对象的对象。这在所有具有指针数据类型的语言中都很常见(例如 Pascal、Ada)。您分配给指针的所有其他属性都来自您自己的个人任意定义。
    【解决方案5】:

    我尝试直接回答OP的问题:

    换句话说,你不能两者都写 int x 和 int * y,且 x 为 a 值,而 y 是一个指针,在任何 那些语言。原因是什么 这背后?

    这背后的原因是这些语言中的托管内存模型。在 C#(或 Python,或 Java,...)中,资源的生命周期和内存的使用由底层运行时自动管理,准确地说,由它的 垃圾收集器 管理。简而言之:应用程序无法控制资源在内存中的位置。它没有被指定——甚至不能保证在资源的生命周期内保持不变。因此,指针作为“虚拟或物理内存中某物的位置”的概念是完全不相关的。

    【讨论】:

      【解决方案6】:

      正如有人已经提到的,如果您有一个庞大的应用程序,指针可能而且实际上会出错。这是我们有时会看到 Windows 由于创建了 NULL 指针而出现问题的原因之一!我个人不喜欢指针,因为它会导致严重的内存泄漏,而且无论您如何管理内存,它最终都会以某种方式追捕您。在处理图像处理应用程序时,我在 OpenCV 上经历了很多。有很多指针浮动,将它们放在一个列表中,然后再检索它们给我带来了问题。但同样,使用指针也有好的方面,它通常是调整代码的好方法。这一切都取决于你在做什么,你必须满足什么规格,等等。

      【讨论】:

        【解决方案7】:

        指针还不错。指针很难。在 Java、C# 等中你不能有 int xint * y 的原因是,这些语言想让你远离解决编码问题(这最终是你的细微错误),他们想要带来您更接近为您的项目提供解决方案。他们希望提高您的工作效率。

        正如我之前所说,指针并不坏,它们只是很难。在 Hello World 程序中,指针似乎是小菜一碟。然而,当程序开始增长时,管理指针、传递正确值、计数对象、删除指针等的复杂性开始变得复杂。

        现在,从程序员(语言的用户,在这种情况下是你)的角度来看,这将导致另一个问题,随着时间的推移会变得很明显:你不这样做的时间越长阅读代码,再次理解它会变得更加困难(即过去几年,几个月或事件日的项目)。除此之外,有时指针会使用不止一层的间接寻址 (TheClass ** ptr)。

        最后但并非最不重要的一点是,指针应用程序在某些主题中非常有用。正如我之前提到的,它们还不错。在算法字段中,它们非常有用,因为在数学上下文中,您可以使用简单的指针(即int *)来引用特定值,并且更改值而不创建另一个对象,具有讽刺意味的是,它变得更容易使用指针而不是不使用指针来实现算法。

        提醒:每次当你问为什么指针或其他东西是坏或好时,试着在历史背景下思考这些主题或技术何时出现以及他们试图解决的问题解决。在这种情况下,指针需要访问贝尔实验室的 PDP 计算机的内存。

        【讨论】:

          猜你喜欢
          • 2012-04-19
          • 1970-01-01
          • 2018-08-15
          • 2020-02-21
          • 2017-04-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多