【问题标题】:NSString pointer: theoretical questions after studyNSString指针:学习后的理论题
【发布时间】:2011-11-14 13:35:03
【问题描述】:

我对 NSString 指针有疑问。我想深入了解这一点,实际上试图创建一个理论,以基于从当时的网络检索到的多个信息来获得充分的理解。当我说我没有偷懒时,请相信我,我实际上阅读了很多,但我仍然有不确定性和疑问。您能否在好/错时确认/否认,并且我已经提出了(?)表示的其他问题和疑问。

我们开始: 如果我考虑这个非常基本的例子:

NSString *sPointer = [[NSString alloc]initWithString:@"This is a pointer"]; 
[sPointer release];

我的出发点是:编译器为指针类型保留 RAM 内存,并且该内存(也有自己的地址)包含存储另一个变量的内存地址(十六进制 - 二进制)(它指向的位置) )。实际的指针会占用大约 2 个字节:

1) 首先是一些一般性问题 - 不一定与目标 C 相关。关于 NSString 指针的实际问题将出现在第 2 点。 字符串是“字符串”,其中 1 个字符占用固定数量的内存空间,比如 2 个字节。 这自动意味着字符串变量占用的内存大小由字符串的长度定义。 然后我在维基百科上读到这个: “在现代字节可寻址计算机中,每个地址标识一个存储字节;数据太大而无法存储在单个字节中,可能驻留在多个字节中,占用一系列连续地址。” 所以在这种情况下,字符串值实际上包含在多个地址中,而不是由单个 1 包含(这已经与我在任何地方读到的不同)(?)。 这些多个地址在现实中是如何包含在 1 个指针中的?指针也会被分成多个地址吗? 您知道计算机中的哪些组件实际识别和分配实际地址“代码”吗?

现在是我的实际问题;

2) 在我的示例中,代码做了两件事:

  • 它创建一个指向存储字符串变量地址的指针。
  • 它实际上还保存了实际的“字符串变量”:@“这是一个指针”,否则将没有任何东西可以指向;

好的,我的问题;我想知道当你释放指针 [sPointer release] 时到底会发生什么;您实际上是在释放指针(包含地址),还是也在从内存中释放实际的“字符串变量”? 我了解到,当您删除引用时,存储实际变量的内存只会在编​​译器需要内存时被覆盖,因此当时不需要清除它。这是错的吗? 如果它是正确的,为什么他们说出于性能原因释放 NSString 指针真的很重要,如果你只是释放基本上只包含几个字节的指针?还是我错了,实际上存储实际变量的内存是否也通过“释放”消息立即清除?

最后还有:原始数据类型没有被释放,但它们在声明时“确实”占用了内存空间(但不超过一个公共指针)。事实上,我们为什么不应该释放它们呢?是什么阻止我们做类似的事情:int i = 5,然后是 [i release]?

很抱歉 - 1 次有很多问题!在实践中,我从来没有遇到过问题,但从理论上讲,我也很想完全理解它——我希望我不是唯一一个。我们可以讨论这个话题吗? 谢谢你,很抱歉打扰!

【问题讨论】:

    标签: objective-c memory pointers nsstring theory


    【解决方案1】:

    在我回答这些问题之前,你先从一个错误的前提开始。在非 16 位系统上,指针占用超过 2 个字节。在 Mac 上,32 位可执行文件占用 4 个字节,64 位可执行文件占用 8 个字节。

    请注意,以下内容并不完全准确(出于优化和其他一些原因,字符串的内部表示有多种, initXXX 函数决定实例化哪些), 不过为了字符串的使用和理解,说明就够了。

    1. NSString 是一个类(也是一个相当复杂的类)。一个字符串,即该类的一个实例,包含一些管理 ivars 和一个是另一个指针,它指向一块足够大的已分配内存,至少可以保存构成字符串的字节/代码点。您的代码(准确地说是 alloc 方法)保留了足够的内存来包含对象的所有 ivars(包括指向缓冲区的指针)并返回指向该内存的指针。这就是你存储在指针中的内容(如果 initWithString: 没有改变它——但我不会在这里讨论,假设它没有改变)。如有必要,initWithString: 分配足够大的缓冲区来保存字符串的文本,并将其内存存储在 NSString 实例内的指针中。所以它是这样的:

       sPointer                            NSString instance        buffer
      +---------------------------+       +-----------------+       +------+
      | addr of NSString instance | ----> | ivar            |   +-> | char |
      +---------------------------+       | ivar            |   |   | char |
                                          | ivar (pointer)  | --+   | char |
                                          | ivar            |       | char |
                                          | etc...          |       | char |
                                          +-----------------+       | char |
                                                                    | etc. |
                                                                    +------+
      
    2. 对于像@"Hello" 这样的硬编码文字字符串,内部指针仅指向该字符串,该字符串已存储在程序中的只读内存中。不用为其分配内存,也不能释放内存。

    但是让我们假设您有一个包含已分配内容的字符串。 release(手动编码或由自动释放池调用)将减少字符串对象的引用计数(所谓的 retainCount)。如果该计数达到零,您的 NSString 类实例将被释放,并且在字符串的 dealloc 方法中,保存字符串文本的缓冲区将被释放。该内存不会以任何方式清除,它只会被内存管理器标记为空闲,这意味着它可以再次用于其他目的。

    【讨论】:

    • 更正:initWithString: 返回交给它的任何内容,在这种情况下是已分配的已保留内存。 stringWithString: 名称中没有 alloc,将返回一个自动释放的对象。
    • Correction2: initWithString: 实际上有时会执行分配。 NSString 的 alloc 方法返回单例。在这种特殊情况下,实际上根本没有分配。
    • @Flyingdiver:正确。我实际上是在考虑 stringWithString。 @bbum:我怀疑 NSString 的 alloc 实际上返回了一个单例。
    • @Rudy bbum 是正确的:+[NSString alloc] 返回一个 NSPlaceholderString 类型的单例。
    • @Fly 这取决于您认为自动释放的对象是什么。有时 +stringWithString: 返回一个对象,当相应的自动释放池被耗尽时将被释放,除非该对象被保留,有时它返回一个永远不会被释放的对象。
    【解决方案2】:

    为了论坛的缘故,我将对您的回答做一个简短而简化的总结作为结论。感谢大家的详细说明,雾气消失了!如果您想添加或纠正某些内容,请随时做出反应:

    • 修正:Mac 上的指针占用 4 个字节的内存空间,而不是 2 个。

    • 指针 *sPointer 指向 NSString 类的一个实例,而不是直接指向保存字符的内存。 NSString 实例由一组 iVar 组成,其中有一个指针 iVar 指向分配的内存,其中存储了组成字符串的 char 变量(在使用 initWithString: 实例方法时定义)。

    • [sPointer 释放];释放消息不是发送到指针本身,而是发送到 NSString 对象的实例。您不是在对指针本身进行操作,而是对指针指向的内容(!)。

    • 发送 alloc 消息时,NSString 实例对象的保留计数增加 1。发送“释放”消息时,并不意味着相关内存实际上被清空,而是减少保留计数减 1。当保留计数达到零时,编译器知道先前分配的内存可以再次使用。

    • 内存地址的呈现方式由操作系统决定。程序中使用的逻辑内存地址与底层实现实际使用的(物理内存地址)不同。

    • LOCAL 变量(不一定是原始变量)存储在堆栈内存中(与存储在堆内存中的对象实例不同)。这意味着它们将在函数结束时自动销毁(它们会自动从堆栈中删除)。有关内存构造堆栈和堆的更多信息可以在几个线程中找到,它们以自己的方式阐明了使用和区别。例如What and where are the stack and heap? / http://ee.hawaii.edu/~tep/EE160/Book/chap14/subsection2.1.1.8.html

    【讨论】:

    • 好的,我会考虑的!我想做一个总结,以帮助其他有同样问题的人。这样,他们不需要阅读所有线程(嗯,他们仍然可以)并有一个摘要。所有人都很有帮助,所以我会全部投票。
    【解决方案3】:

    这些多个地址在现实中是如何包含在1个指针中的? 指针也会被分成多个地址吗?你 知道计算机中的哪些组件实际识别和分配 实际地址“代码”?

    从程序员的角度来看(注意这一点),指针本身通常是一个 4 字节的数字,表示从内存开始的偏移量(32 位,在 64 位中你可以有地址向上到 8 个字节)。问题是这个指针指向正在存储的任何内容的开始,仅此而已。

    例如,在 C 中,原始字符串使用 NULL (\0) 终止字符串来标识字符串何时结束(尝试在 C 上使用非零结尾字符串执行 printf(),它将打印内存,直到它找到一个零)。这当然是非常危险的,并且必须使用像strncpy(注意“n”)这样的函数来指示您应该手动输入从偏移量到结束的字符数。

    避免这种情况的一种方法是将已用空间存储在内存地址的开头,例如

    struct
    {
        int size;
        char *string;
    }string;
    

    存储大小以防止出现任何问题。 Objective-C 和许多其他更抽象的语言以自己的方式实现如何处理内存。 NSString* 是一个非常抽象的类,用于了解幕后发生的事情,它可能从 NSObject 继承了所有内存管理。

    我试图得到的重点是一个指针包含起始地址,您可以从那里逐字节跳转(或一定大小的跳转),保持记住你存储的任何东西的总长度,以避免做一些讨厌的事情,比如溢出堆栈内存(因此,这个网站的名字)。

    现在,计算机如何提供这些地址完全取决于操作系统,并且您在所有程序中使用的逻辑内存地址与底层实现使用的(物理内存地址)完全不同。通常,您会发现内存存储在称为“帧”的分段单元中,使用过的帧称为“page”。物理和逻辑之间的映射是通过“[Page Table]”2 完成的。

    如您所见,该软件几乎可以处理所有事情,但这并不意味着没有硬件支持这一点,例如 TLB,这是一个 cpu 级缓存,用于保存最近的内存地址以便快速访问。

    另外,请对我的回答持保留态度,我已经有一段时间没有研究这些主题了。

    好的,我的问题;我想知道当你释放 指针[sPointer 释放];你真的在释放指针吗 (包含地址),或者您是否还发布了实际的“字符串 变量”也从内存中?我了解到,当您删除 参考,存储实际变量的内存将只是 在编译器需要内存时被覆盖,因此它不需要 到时候清零。这是错的吗?如果是正确的,为什么要这样做 他们说释放 NSString 指针真的很重要 出于性能原因,如果您只是释放指针 基本上只包含几个字节?还是我错了,是记忆 实际上存储实际变量的位置也立即清除 “释放”消息?

    当您释放时,您只是在减少对象的内存计数。你的意思是当它被释放时会发生什么(当计数达到零时)。

    当你dealloc 某事时,你基本上是在说它被保留的空间现在可以被任何其他请求内存的东西(通过分配)替换。该变量可能仍指向一个已释放的空间,这会导致问题(阅读有关悬空指针和泄漏的信息)。

    内存可能会被清除,但不能保证

    我希望这能消除所有疑虑,因为所有这些疑虑都源于您对内存释放的困惑。

    最后还有:原始数据类型没有被释放,但它们“会” 在声明时占用内存空间(但不超过 公共指针)。事实上,我们为什么不应该释放它们呢?是什么阻止了 我们从做类似的事情:int i = 5,然后是 [i release]?;

    问题是 C 有两件主要的事情(实际上,还有更多): 存储已使用 alloc(或 C 中的 malloc)请求的内存的堆,这些需要被释放。而存放局部变量的堆栈会在函数/块结束时死亡(堆栈弹出函数调用)。

    在您的示例中,变量i 已在其范围内本地声明,并且在堆栈中继续。尝试执行 dealloc/free(另外,变量我不会响应 release,也不会 dealloc,因为它不是对象)将不起作用,因为不是需要释放的内存类型。

    我建议您在尝试解决 Objective-C 所做的事情之前先回到 C,因为很难清楚地了解命令式编程如何与 release 和 dealloc 等所有不错的抽象一起工作。

    【讨论】:

      【解决方案4】:

      也许我错了,但我昨天刚读到指针通常需要 4 个字节。这没有回答你的任何问题,但你似乎对此很感兴趣,所以我想我会提到它。

      我认为您混淆的根源在于您将原语与 Objective-C 类混淆了。 Objective-C 类(或确切地说是类的实例)可以接受消息(类似于其他语言中的方法调用)。 retain 就是这样一条消息。这就是为什么一个Objective-C NSString 对象可以接收retain 消息,但不能接收像整数这样的原语。我认为这是你的另一个困惑。 retainrelease 等不是 Objective-C 语言结构,它们是您发送给对象的实际消息(思考方法)。这就是为什么它们适用于 Objective-C 对象而不适用于像整数和浮点数这样的原语。

      另一个类似的困惑是,您所读到的有关如何存储字符串的内容更多地与 C 风格的字符串有关,例如 char *name = "john"。但是,当您创建一个指向NSString 的指针时,它指向一个NSString instance,它本身决定如何处理存储实际的字符串字节/字符。这可能与 C 字符串的存储方式相同,也可能不同。

      数据太大而无法存储在单个字节中,可能会驻留在占用连续地址序列的多个字节中。 " 所以在这种情况下,字符串值实际上是由多个地址而不是单个 1 包含的(这已经与我在各处读到的不同了)(?)。这些多个地址实际上是如何包含在 1 个指针中的?

      例如,在 C 中,指针将指向字符串中第一个字符的地址。

      好的,我的问题;我想知道当你释放指针 [sPointer release] 时到底会发生什么;您实际上是在释放指针(包含地址),还是也在从内存中释放实际的“字符串变量”?

      您正在向NSString 实例/对象发送release 消息。需要注意这一点以避免进一步混淆。您不是在对指针本身进行操作,而是对指针指向的对象,即NSString 对象进行操作。所以你没有释放指针本身。在向对象发送release 方法后,如果它的引用计数已达到0,那么它将通过释放它存储的所有内容来处理自身的释放,我想这包括实际的字符串。

      如果是正确的,为什么他们说释放 NSString 指针出于性能原因真的很重要,如果你只是释放基本上只包含几个字节的指针?

      是的,您实际上是在将release 消息发送到字符串实例,而 会在必要时处理如何释放自身。如果您只是简单地擦除指针,使其不再指向字符串实例,那么您将不再知道在哪里/如何访问存储在该位置的数据,但它不会让它神奇地消失,程序不会自动知道它可以使用该内存。您所暗示的是garbage collection,简单地说,未使用的内存将自动释放以供后续使用。 Objective-C 2.0 确实有垃圾收集,但据我所知,它还没有在 iOS 设备上启用。相反,新版本的 iOS 将支持称为 Automatic Reference Counting 的功能,其中编译器自己负责引用计数。

      对不起,如果我没有回答你所有的问题,你问了很多 :P 如果我的任何信息有误,请告诉我!我试图将我的答案限制在我认为我确实知道的范围内。

      【讨论】:

        【解决方案5】:

        “或者我错了,实际上存储实际变量的内存是否也随着“释放”消息立即清除?内存没有被清除,而是进入空闲内存池,因此它确实减少了程序的内存打印。如果您没有释放指针,您将继续“占用”内存,直到您耗尽所有可用的虚拟内存,并且不仅会导致您的程序崩溃,还会导致系统崩溃。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2010-09-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-05-16
          • 2013-06-18
          相关资源
          最近更新 更多