不寻常架构的C指针算法

时间:2018-06-04 19:10:47

标签: c pointers language-lawyer pointer-arithmetic

我正在努力更好地理解C标准。特别是我对指针算法如何在异常机器架构的实现中起作用感兴趣。

假设我有一个64位宽寄存器的处理器连接到RAM,其中每个地址对应一个4位宽的单元。这台机器的C实现将CHAR_BIT定义为等于8.假设我编译并执行以下代码行:

char *pointer = 0;
pointer = pointer + 1;

执行后,指针等于1.这给人的印象是,通常char类型的数据对应于机器上最小的可寻址存储单元。

现在假设我有一个12位宽寄存器的处理器连接到RAM,其中每个地址对应一个4位宽的单元。此机器的C实现将CHAR_BIT定义为等于12.假设为此机器编译和执行相同的代码行。指针是否等于3?

更一般地说,当你将指针递增到char时,地址是否等于CHAR_BIT除以机器上存储单元的宽度?

6 个答案:

答案 0 :(得分:3)

  

指针是否等于3?

嗯,标准并没有说明如何实现指针。该标准告诉您在以特定方式使用指针时会发生什么,而不是指针的值应该是什么。

我们所知道的是,向char指针添加1将使指针指向下一个char对象 - 无论如何。但没有关于指针的价值。

所以当你这么说时

pointer = pointer + 1;

会使指针等于1,这是错误的。该标准对此没有任何说明。

在大多数系统中,char为8位,指针是引用8位可寻址内存的(虚拟)内存地址。在这样的系统上,递增一个char指针会使指针值(也就是内存地址)增加1.然而,在 - 不寻常的架构 - 没有办法说出来。

但是如果你有一个系统,其中每个内存地址引用4位而char是12位,那么++pointer将指针增加3是一个很好的猜测。

答案 1 :(得分:0)

指针的增加量是指数"指向"的数据类型宽度的最小值,但不能保证精确增加到该大小。

出于内存对齐的目的,有很多次指针可能会增加到超过最小宽度的下一个内存字对齐。

所以,一般来说,你不能假设这个指针等于3.它很可能是3,4或更大的数字。

这是一个例子。

struct char_three {
   char a;
   char b;
   char c;
};

struct char_three* my_pointer = 0;
my_pointer++;

/* I'd be shocked if my_pointer was now 3 */

内存对齐是特定于机器的。除非大多数机器将WORD定义为可以与总线上的内存提取对齐的第一个地址,否则无法对其进行概括。某些机器可以指定不与总线提取对齐的地址。在这种情况下,选择跨越对齐的两个字节可能会导致加载两个WORDS。

大多数系统在不抱怨的情况下不接受非对齐边界上的WORD加载。这意味着如果需要最大密度,则应用一些锅炉板组件来将提取转换为前进的WORD边界。

大多数编译器更喜欢速度到最大数据密度,因此他们将结构化数据对齐以利用WORD边界,避免额外的计算。这意味着在许多情况下,未仔细对齐的数据可能包含" hole"未使用的字节数。

如果您对上述摘要的详细信息感兴趣,可以阅读Data Structure Alignment,其中将讨论对齐(以及因此)填充。

答案 2 :(得分:0)

  

char *pointer = 0;
  执行后,指针等于1

不一定。 这种特殊情况为您提供了一个空指针,因为0是一个空指针常量。严格地说,这样的指针不应该指向有效的对象。如果你看一下存储在指针中的实际地址,它可以是任何东西。

除了空指针之外,C语言希望您通过首先指向数组来进行指针运算。或者在char的情况下,您还可以指向一大块通用数据,例如结构。其他一切,就像你的例子一样,是未定义的行为。

  

此机器的C实现将CHAR_BIT定义为等于12

C标准将char定义为等于一个字节,因此您的示例有点奇怪且矛盾。指针算术将始终增加指向指向数组中下一个对象的指针。标准根本没有说明地址的表示,但你的虚构的例子会明显地将地址增加12位,因为它的大小是char

即使从学习的角度来看,虚构的计算机也无法进行讨论。我建议专注于现实世界的计算机。

答案 3 :(得分:0)

似乎这个问题的混淆来自于C标准中的“字节”一词没有典型的定义(8位)。具体来说,C标准中的“字节”一词表示一个位集合,其中位数由实现定义的常量CHAR_BITS指定。此外,C标准定义的“字节”是C程序可以访问的最小可寻址对象

这就留下了一个问题,即“可寻址”的C定义与“可寻址”的硬件定义之间是否存在一对一的对应关系。换句话说,硬件是否可以处理小于“字节”的对象?如果(如在OP中)“字节”占据3个地址,那么这意味着“字节”访问具有对齐限制。也就是说3和6是有效的“字节”地址,但4和5不是。第6.2.8节禁止这一点,它讨论了对象的对齐方式。

这意味着OP提出的体系结构规范不支持 。特别是,当CHAR_BIT等于12时,实现可能没有指向4位对象的指针。

以下是C标准的相关章节:

§3.6标准中使用的“字节”的定义

  

[一个字节是]可寻址的数据存储单元,足以容纳   执行环境的基本字符集的任何成员。

     

注1:可以表示每个字节的地址   唯一的对象。

     

注2:一个字节由一个连续的位序列组成   其中是实现定义的。最重要的一点是   称为低位;最重要的一点叫做   高阶位。

§5.2.4.2.1将CHAR_BIT描述为

  

不是位字段(字节)的最小对象的位数

§6.2.6.1将所有大于char的对象限制为CHAR_BIT位的倍数:

  

[...]   除了位域之外,对象由连续的序列组成   一个或多个字节,其数量,顺序和编码都是   明确指定或实现定义。

     

[...]存储在任何其他对象类型的非位字段对象中的值   由n×CHAR_BIT位组成,其中n是其对象的大小   type,以字节为单位。

§6.2.8限制对象的对齐

  

完整对象类型具有对齐要求   对该类型对象的地址的限制   分配。对齐是实现定义的整数值   表示连续地址之间的字节数   可以分配给定的对象。

     

有效对齐仅包括_Alignof返回的值   基本类型的表达式,另外还有一个   实现定义的值集,可以为空。的每   有效对齐值应为2的非负整数幂

§6.5.3.2指定sizeof a char,因此指定“字节”

  

当sizeof应用于类型为char,unsigned的操作数时   char,或signed char,(或其合格版本)的结果是   1。

答案 4 :(得分:0)

  

当您将指针递增到char时,地址是否等于CHAR_BIT除以机器上内存单元格的宽度?

在"传统"机器 - 确实在C运行的绝大多数机器上 - CHAR_BIT只是 机器上的存储器单元的宽度,所以问题的答案是空的&#34 ;是" (因为CHAR_BIT / CHAR_BIT是1。)。

内存单元小于CHAR_BIT的机器非常非常奇怪 - 可以说与C&C的定义不相容。

C&C的定义说:

  • sizeof(char)正好是1。

  • CHAR_BITchar中的位数至少为8.也就是说,就C而言,一个字节可能不小于8位。 (它可能更大,这对很多人来说都是一个惊喜,但这并不关心我们。)

  • 强烈建议(如果不是明确要求)char(或"字节")是机器"最小可寻址单元&# 34;或者其他一些。

因此,对于一次可以寻址4位的机器,我们必须为sizeof(char)CHAR_BIT选择不自然的值(否则可能需要24分别为{1}},我们必须忽略类型char是机器的最小可寻址单元的建议。

C不强制指针的内部表示(位模式)。最接近的可移植C程序可以使用指针值的内部表示来执行任何操作,使用%p将其打印出来 - 并且明确定义为实现定义。

所以我认为在" 4位"上实现C的唯一方法机器将涉及代码

char a[10];
char *p = a;
p++;

生成指令,实际上将p后面的地址增加2。

这将是一个有趣的问题,%p是否应该打印实际的原始指针值,或者值是否除以2。

观看随后的烟花也会很有趣,因为在这样的机器上太聪明的程序员使用类型惩罚技术来获取指针的内部值,以便他们可以通过实际< / em> 1 - 不是那个&#34;正确的&#34;增加1将总是产生 - 这样他们可以通过访问一个字节的奇怪的nybble来惊奇他们的朋友,或者通过询问有关它的问题来混淆常客。 &#34;我只是将char指针递增1.为什么%p显示的值更大?&#34;

答案 5 :(得分:0)

以下代码片段演示了C指针算法的不变 - 无论CHAR_BIT是什么,无论硬件最小可寻址单元是什么,无论实际位是什么指针的表示是,

#include <assert.h>
int main(void)
{
    T x[2]; // for any object type T whatsoever
    assert(&x[1] - &x[0] == 1); // must be true
}

根据定义sizeof(char) == 1 ,这也意味着

#include <assert.h>
int main(void)
{
    T x[2]; // again for any object type T whatsoever
    char *p = (char *)&x[0];
    char *q = (char *)&x[1];
    assert(q - p == sizeof(T)); // must be true
}

但是,如果在执行减法之前转换为整数,则不变量会蒸发:

#include <assert.h>
#include <inttypes.h>
int main(void);
{
    T x[2];
    uintptr_t p = (uintptr_t)&x[0];
    uintptr_t q = (uintptr_t)&x[1];
    assert(q - p == sizeof(T)); // implementation-defined whether true
}

因为通过将指针转换为相同大小的整数(反之亦然)而执行的转换是实现定义的。我认为它必须是双射的,但我可能错了,并且绝对不需要保留任何上述不变量。