我想知道 违反假设的架构 我在下面列出了。另外,我想知道是否有任何假设对于所有架构都是错误的(也就是说,是否有任何假设完全错误)。

  1. sizeof(int *) == sizeof(char *) == sizeof(void *) == sizeof(func_ptr *)

  2. 无论指向的数据类型如何,给定体系结构的所有指针在内存中的表示形式都是相同的。

  3. 指针在内存中的表示形式与架构相同位长的整数相同。

  4. 指针数据类型的乘法和除法仅被编译器禁止。笔记:是的,我知道这是无稽之谈。我的意思是 - 是否有硬件支持来禁止这种不正确的使用?

  5. 所有指针值都可以转换为单个整数。换句话说,哪些架构仍然使用段和偏移量?

  6. 增加一个指针相当于添加 sizeof(the pointed data type) 到指针存储的内存地址。如果 p 是一个 int32* 然后 p+1 等于后面4个字节的内存地址 p.

我最习惯在连续的虚拟内存空间中使用指针。对于这种用法,我通常可以将它们视为数轴上的地址。请参阅堆栈溢出问题 指针比较.

有帮助吗?

解决方案

我无法为您提供所有这些的具体示例,但我会尽力而为。

sizeof(int *) == sizeof(char *) == sizeof(void *) == sizeof(func_ptr *)

我不知道有什么系统可以让我 知道 这是错误的,但请考虑:

移动设备通常具有一定量的只读存储器,其中存储程序代码等。可以想象,只读值(常量变量)可以存储在只读存储器中。并且由于 ROM 地址空间可能小于普通 RAM 地址空间,因此指针大小也可能不同。同样,指向函数的指针可能具有不同的大小,因为它们可能指向程序加载到的只读内存,否则无法修改(因此您的数据无法存储在其中)。

因此,我不知道在哪些平台上我观察到上述内容不成立,但我可以想象可能出现这种情况的系统。

无论指向的数据类型如何,给定体系结构的所有指针在内存中的表示形式都是相同的。

考虑成员指针与常规指针。它们没有相同的表示(或大小)。成员指针由 this 指针和偏移量。

如上所述,可以想象,某些 CPU 会将常量数据加载到单独的内存区域中,该区域使用单独的指针格式。

指针在内存中的表示形式与架构相同位长的整数相同。

取决于如何定义该位长度。:) 一个 int 在许多 64 位平台上仍然是 32 位。但指针是 64 位的。如前所述,具有分段内存模型的 CPU 将具有由一对数字组成的指针。同样,成员指针由一对数字组成。

指针数据类型的乘法和除法仅被编译器禁止。

最终,仅指针数据类型 存在 在编译器中。CPU处理的不是指针,而是整数和内存地址。所以没有其他地方可以对指针类型进行这些操作 可以 被禁止。您不妨要求 CPU 禁止 C++ 字符串对象的串联。它不能这样做,因为 C++ 字符串类型仅存在于 C++ 语言中,而不存在于生成的机器代码中。

不过,要回答你的问题 意思是, ,查找 Motorola 68000 CPU。我相信他们有单独的整数和内存地址寄存器。这意味着他们可以轻松禁止这种无意义的操作。

所有指针值都可以转换为单个整数。

你在那里很安全。C 和 C++ 标准保证这始终是可能的,无论内存空间布局、CPU 架构和其他因素如何。具体来说,他们保证 实现定义的映射. 。换句话说,您始终可以将指针转换为整数,然后将该整数转换回原始指针。但是 C/C++ 语言没有说明中间整数值应该是什么。这取决于各个编译器及其目标硬件。

增加一个指针相当于将sizeof(指向的数据类型)添加到指针存储的内存地址上。

再次强调,这是有保证的。如果从概念上考虑,指针并不指向地址,而是指向 目的, ,那么这就完全有道理了。显然,给指针加一将使其指向 下一个 目的。如果一个对象的长度为 20 个字节,则增加指针将使其移动 20 个字节,以便它移动到下一个 目的.

如果一个指针仅仅是线性地址空间中的一个内存地址,如果它基本上是一个整数,那么递增它就会给该地址加 1——也就是说,它将移动到下一个 字节.

最后,正如我在对您的问题的评论中提到的,请记住 C++ 只是一种语言。它不关心它被编译成哪种架构。这些限制中的许多对于现代 CPU 来说可能看起来很模糊。但如果您的目标是以前的 CPU 该怎么办?如果您的目标是未来十年的 CPU,该怎么办?您甚至不知道它们将如何工作,因此您无法对它们做出太多假设。如果您的目标是虚拟机怎么办?编译器已经存在,可以为 Flash 生成字节码,准备好从网站运行。如果您想将 C++ 源代码编译为 Python 源代码怎么办?

遵守标准中指定的规则可以保证您的代码可以在 全部 这些案例。

其他提示

我心里没有具体的现实例子,但“权威”是 C 标准。如果标准没有要求某些内容,您可以构建一个故意不遵守任何其他假设的一致实现。其中一些假设在大多数情况下都是正确的,只是因为将指针实现为表示可以由处理器直接获取的内存地址的整数很方便,但这只是“方便”的结果,不能被视为一个普遍的真理。

  1. 标准没有要求(看到这个问题)。例如, sizeof(int*) 可以不等于 size(double*). void* 保证能够存储任何指针值。
  2. 标准没有要求。根据定义,尺寸是表征的一部分。如果大小可以不同,则表示也可以不同。
  3. 不必要。事实上,“架构的位长”是一个模糊的说法。64 位处理器到底是什么?是地址总线吗?寄存器的大小?数据总线?什么?
  4. “乘”或“除”指针是没有意义的。编译器禁止这样做,但您当然可以乘法或除法底层表示(这对我来说实际上没有意义),这会导致未定义的行为。
  5. 也许我不明白你的意思但是 一切 在数字计算机中只是某种二进制数。
  6. 是的;有点儿。它保证指向一个位置 sizeof(pointer_type) 更远。它不一定等同于数字的算术加法(即 更远 这里是一个逻辑概念。实际表示是特定于架构的)

对于 6.:指针不一定是内存地址。参见例如“大指针阴谋“来自 Stack Overflow 用户 贾尔夫:

是的,我在上面的评论中使用了“地址”这个词。重要的是要了解我的意思。我的意思并不是“物理存储数据的内存地址”,而只是一个抽象的“我们需要什么来定位值”。i 的地址可能是任何东西,但是一旦我们有了它,我们就可以随时找到并修改 i。”

和:

指针不是内存地址!我上面已经提到过这一点,但我们再说一遍。指针通常由编译器简单地实现为内存地址,是的,但并非必须如此。”

有关 C99 标准中的指针的一些进一步信息:

  • 6.2.5 §27 保证 void*char* 具有相同的表示形式,即它们可以互换使用而无需转换,即相同的地址由相同的位模式表示(对于其他指针类型不一定如此)
  • 6.3.2.3 §1 规定任何指向不完整或对象类型的指针都可以转换为(或从) void* 再次返回仍然有效;这不包括函数指针!
  • 6.3.2.3 §6 规定 void* 可以转换为(或从)整数,并且 7.18.1.4 §1 提供了适当的类型 intptr_tuintptr_t;问题:这些类型是可选的 - 标准明确提到不需要有足够大的整数类型来实际保存指针的值!

sizeof(char*) != sizeof(void(*)(void) ?- 不在 36 位寻址模式下的 x86 上(自 Pentium 1 以来几乎所有 Intel CPU 都支持)

“指针在内存中的表示形式与相同位长度的整数相同” - 在任何现代架构上都没有内存中的表示形式;标记内存从未流行起来,并且在 C 标准化之前就已经过时了。事实上,内存甚至不保存整数,只保存位和可以说是字(不是字节;大多数物理内存不允许您只读取 8 位。)

“指针相乘是不可能的”——68000家族;地址寄存器(保存指针的寄存器)不支持 IIRC。

“所有指针都可以转换为整数” - 不适用于 PIC。

“增加 T* 相当于将 sizeof(T) 添加到内存地址” - 根据定义是正确的。也相当于 &pointer[1].

我不知道其他的,但对于 DOS,#3 中的假设是不正确的。DOS 是 16 位的,并使用各种技巧来映射许多超过 16 位的内存。

指针在内存中的表示形式与架构相同位长的整数相同。

我认为这个假设是错误的,因为例如在 80186 上,一个 32 位指针保存在两个寄存器中(一个偏移寄存器和一个段寄存器),并且在访问期间哪个半字进入哪个寄存器很重要。

指针数据类型的乘法和除法仅被编译器禁止。

您不能对类型进行乘法或除法。;P

我不确定你为什么要乘或除指针。

所有指针值都可以转换为单个整数。换句话说,哪些架构仍然使用段和偏移量?

C99 标准允许指针存储在 intptr_t, ,这是一个整数类型。所以,是的。

增加一个指针相当于将sizeof(指向的数据类型)添加到指针存储的内存地址上。如果 p 是 int32*,则 p+1 等于 p 之后 4 个字节的内存地址。

x + y 在哪里 x 是一个 T *y 是一个整数,等于 (T *)((intptr_t)x + y * sizeof(T)) 据我所知。对齐可能是一个问题,但可以在 sizeof. 。我不太确定。

一般来说,所有问题的答案都是“是的”,这是因为只有那些直接实现流行语言的机器才得以问世并持续到本世纪。尽管语言标准保留改变这些“不变量”或断言的权利,但这种情况在实际产品中从未发生过,除了第 3 条和第 4 条可能例外,它们需要一些重述才能普遍正确。

当然可以构建分段的 MMU 设计,它大致对应于过去几年学术界流行的基于功能的架构,但通常没有这样的系统在启用此类功能的情况下得到普遍使用。这样的系统可能与断言相冲突,因为它可能有大指针。

除了通常具有大指针的分段/功能 MMU 之外,更极端的设计还尝试在指针中编码数据类型。其中很少有人建造过。(这个问题提出了基本的面向字的​​指针即字架构的所有替代方案。)

具体来说:

  1. 无论指向的数据类型如何,给定体系结构的所有指针在内存中的表示形式都是相同的。 确实如此,除了过去极其古怪的设计试图不在强类型语言中而是在硬件中实现保护。
  2. 指针在内存中的表示形式与架构相同位长的整数相同。 也许,某种整数类型肯定是相同的,请参阅 LP64 与 LLP64.
  3. 指针数据类型的乘法和除法仅被编译器禁止。 正确的.
  4. 所有指针值都可以转换为单个整数。换句话说,哪些架构仍然使用段和偏移量? 如今,除了 C 之外,没有任何东西使用段和偏移量 int 通常不够大,您可能需要一个 long 或者 long long 握住指针。
  5. 增加一个指针相当于将sizeof(指向的数据类型)添加到指针存储的内存地址上。如果 p 是 int32*,则 p+1 等于 p 之后 4 个字节的内存地址。 是的。

有趣的是,每个英特尔架构 CPU,即每个 PeeCee,都包含一个具有史诗般、传奇性和复杂性的精心设计的分段单元。然而,它实际上被禁用了。每当 PC 操作系统启动时,它都会将段基数设置为 0,将段长度设置为 ~0,从而清零段并提供平面内存模型。

在 20 世纪 50 年代、1960 年代和 1970 年代有很多“字寻址”架构。但我不记得有任何带有 C 编译器的主流示例。我记得 ICL / 三河 PERQ 机器 在 20 世纪 80 年代,它采用字寻址并具有可写控制存储(微代码)。它的一个实例有一个 C 编译器和一种 Unix 风格,称为 PNX, ,但 C 编译器需要特殊的微代码。

基本问题是 char* 类型在字寻址机器上很尴尬,无论你如何实现它们。你经常与 sizeof(int *) != sizeof(char *) ...

有趣的是,在 C 之前有一种语言叫做 BCPL 其中基本指针类型为字地址;也就是说,递增指针可以得到下一个字的地址,并且 ptr!1 给你的话 ptr + 1. 。有一个不同的运算符用于寻址字节: ptr%42 如果我记得的话。

编辑:血糖低时不要回答问题。你的大脑(当然是我的)并没有按照你的预期工作。:-(

小挑剔:

p 是 int32* 那么 p+1

是错误的,它需要是 unsigned int32,否则它将在 2GB 处换行。

有趣的奇怪之处 - 我从 Transputer 芯片的 C 编译器的作者那里得到了这个 - 他告诉我,对于该编译器,NULL 被定义为 -2GB。为什么?因为晶片机有一个有符号的地址范围:-2GB 至 +2GB。你能相信吗?很神奇不是吗?

此后我遇到了很多人,他们告诉我这样定义 NULL 是错误的。我同意,但如果你不这样做,你最终会在地址范围的中间出现空指针。

我想我们大多数人都会庆幸我们没有在开发晶片机!

我想知道违反我下面列出的假设的体系结构。

我看到 Stephen C 提到了 PERQ 机器,MSalters 提到了 68000 和 PIC。

令我失望的是,没有人通过命名任何奇怪而美妙的架构来真正回答这个问题,这些架构具有符合标准的 C 编译器,但不符合某些无根据的假设。

sizeof(int *)== sizeof(char *)== sizeof(void *)== sizeof(func_ptr *)?

不必要。一些例子:

大多数用于哈佛大学的编译器8位处理器 - PIC和8051和M8C-使SizeOf(int *)== sizeof(char *),但与sizeof(func_ptr *)不同。

这些系列中的一些非常小的芯片具有 256 字节的 RAM(或更少),但有几千字节的 PROGMEM(闪存或 ROM),因此编译器通常使 sizeof(int *) == sizeof(char *) 等于 1(a单个 8 位字节),但 sizeof(func_ptr *) 等于 2(两个 8 位字节)。

对于那些具有几千字节 RAM 和 128 千字节左右 PROGMEM 的系列中的许多较大芯片的编译器,使 sizeof(int *) == sizeof(char *) 等于 2(两个 8 位字节),但 sizeof( func_ptr *) 等于 3(三个 8 位字节)。

一些哈佛架构芯片可以精确存储 2^16(“64KByte”)的 PROGMEM(闪存或 ROM),以及另外 2^16(“64KByte”)的 RAM + 内存映射 I/O。这种芯片的编译器使 sizeof(func_ptr *) 始终为 2(两个字节);但通常有办法使其他类型的指针 sizeof(int *) == sizeof(char *) == sizeof(void *) 变成“long ptr” 3字节通用指针 它有一个额外的魔术位,指示该指针是指向 RAM 还是 PROGMEM。(当您从许多不同的子例程调用该函数时,您需要传递给“print_text_to_the_LCD()”函数的指针类型,有时使用缓冲区中变量字符串的地址,该地址可能位于 RAM 中的任何位置,有时使用一个许多常量字符串可能位于 PROGMEM 中的任何位置)。此类编译器通常有特殊的关键字(“short”或“near”、“long”或“far”)来让程序员在同一个程序中专门指示三种不同类型的char指针——只需要2个字节来指示的常量字符串在 PROGMEM 中,它们位于非常量字符串中,只需要 2 个字节来指示它们在 RAM 中的位置,以及“print_text_to_the_LCD()”接受的 3 字节指针类型。

大多数 20 世纪 50 年代和 1960 年代制造的计算机都使用 36位字长 或一个 18位字长, ,具有 18 位(或更少)地址总线。我听说此类计算机的 C 编译器经常使用 9位字节,带有sizeof(int *)== sizeof(func_ptr *)= 2的size = 2,它给出18位,因为所有整数和函数都必须单词平衡;但是 sizeof(char *) == sizeof(void *) == 4 可以利用 特殊 PDP-10 说明 将此类指针存储在完整的 36 位字中。该完整的 36 位字包括一个 18 位字地址,以及其他 18 位中的一些位(除其他外)指示该字中所指向的字符的位位置。

对于给定体系结构的所有指针的内存表示,无论指向的数据类型是相同的吗?

不必要。一些例子:

在我上面提到的任何一种架构上,指针都有不同的大小。那么他们怎么可能有“相同”的代表呢?

某些系统上的某些编译器使用 “描述符” 实现字符指针和其他类型的指针。这样的描述符是 不同的 对于指向“”中第一个“字符”的指针char big_array[4000]" 比指向 " 中第一个 "char" 的指针char small_array[10]”,这可以说是不同的数据类型,即使小数组恰好从大数组先前占用的内存中完全相同的位置开始。描述符允许此类机器捕获并捕获在其他机器上导致此类问题的缓冲区溢出。

《低脂指点》 SAFElite 和类似的“软处理器”中使用的具有关于指针指向的缓冲区大小的类似“额外信息”。Low-Fat 指针在捕获和捕获缓冲区溢出方面具有相同的优点。

指针的内存表示与与体系结构的位相同的整数相同?

不必要。一些例子:

“标记架构” 在机器中,内存的每个字都有一些位来指示该字是整数、指针还是其他东西。对于这样的机器,查看标签位就会告诉您该字是整数还是指针。

我听说Nova小型机有一个 “间接位” 在每一个激发灵感的词中 “间接线程代码”. 。听起来就像存储一个整数会清除该位,而存储一个指针会设置该位。

指针数据类型的乘法和划分仅由编译器禁止。笔记:是的,我知道这是无稽之谈。我的意思是 - 是否有硬件支持禁止这种错误的用法?

是的,某些硬件不直接支持此类操作。

正如其他人已经提到的,68000 和 6809 中的“乘法”指令仅适用于(某些)“数据寄存器”;它们不能直接应用于“地址寄存器”中的值。(编译器很容易解决这些限制——将这些值从地址寄存器移动到适当的数据寄存器,然后使用 MUL)。

所有指针值都可以转换为单一数据类型吗?

是的。

为了 memcpy() 可以正常工作, ,C 标准要求每种类型的指针值都可以转换为 void 指针(“void *”)。

即使对于仍然使用段和偏移量的体系结构,编译器也需要完成这项工作。

所有指针值都可以转换为单个整数吗?换句话说,哪些架构仍然利用细分市场和偏移?

我不知道。

我怀疑所有指针值都可以转换为“size_t”和“ptrdiff_t”中定义的整数数据类型<stddef.h>".

增加指针等效于将大小(指向数据类型)添加到指针存储的内存地址。如果p为int32*,则p+1等于p之后的内存地址4字节。

目前尚不清楚你在这里问什么。

问:如果我有某种结构或原始数据类型的数组(例如,“#include <stdint.h> ... int32_t example_array[1000]; ..."),然后我增加一个指向该数组的指针(例如,"int32_t p = &example_array[99];...p++;...”),指针现在是否指向该数组的下一个连续成员,即内存中更远的 sizeof(指向的数据类型)字节?

A:是的,编译器必须使指针在递增一次后,指向数组中下一个独立的连续 int32_t,sizeof(指向的数据类型)字节,以便符合标准。

问:那么,如果 p 是 int32* ,那么 p+1 等于 p 之后 4 个字节的内存地址?

A:当 sizeof( int32_t ) 实际上等于 4 时,是的。否则,例如对于某些可字寻址机器,包括一些现代 DSP,其中 sizeof( int32_t ) 可能等于 2 甚至 1,则 p+1 等于 p 之后 2 甚至 1 个“C 字节”的内存地址。

问:因此,如果我获取指针,并将其转换为“int”......

A:一种“全世界都是 VAX 异端”。

问:...然后将“int”转换回指针......

A:另一种类型的“全世界都是 VAX 异端”。

问:因此,如果我采用指向 int32_t 的指针 p ,并将其转换为足够大以包含该指针的整数类型,然后添加 sizeof( int32_t ) 到该整型,然后将该整型类型转换回指针——当我执行所有这些操作时,结果指针等于 p+1?

不必要。

许多 DSP 和其他一些现代芯片都具有面向字的寻址,而不是 8 位芯片使用的面向字节的处理。

此类芯片的一些 C 编译器将 2 个字符塞入每个字中,但需要 2 个这样的字来保存 int32_t ——所以他们报告说 sizeof( int32_t ) 是 4。(我听说有一个 C 编译器 24位 摩托罗拉 56000 就是这样做的)。

编译器需要进行一些安排,以便对指向 int32_t 的指针执行“p++”会增加指向下一个 int32_t 值的指针。编译器有多种方法可以做到这一点。

一种符合标准的方法是将每个指向 int32_t 的指针存储为“本机字地址”。因为需要 2 个字来保存单个 int32_t 值,所以 C 编译器编译“int32_t * p; ... p++" 转换为某种汇编语言,该语言将指针值增加 2。另一方面,如果那个人“int32_t * p; ... int x = (int)p; x += sizeof( int32_t ); p = (int32_t *)x;”,56000 的 C 编译器可能会将其编译为将指针值增加 4 的汇编语言。

我最习惯了指针在连续的虚拟内存空间中使用。

几个PIC和8086和其他系统具有非连续的RAM - 在“使硬件变得更简单”的地址上的几个RAM。使用内存映射 I/O 或根本不附加任何内容到这些块之间的地址空间间隙。

这比听起来更尴尬。

在某些情况下——例如 位带硬件 用于避免由以下原因引起的问题 读-修改-写 -- RAM 中完全相同的位可以使用 2 个或更多不同的地址来读取或写入。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top