计算机如何分配两个变量,我们如何计算两个变量之间的距离?

2022-06-06 00:00:00 c c++ cpu-architecture

当我试图检查两个变量之间的差异时,我发现了一些有趣的东西(您可以在下面的代码中看到)

#include <stdio.h>
#include <conio.h>
int main() {
    int a, b;  
    printf("%d", (int)&a - (int)&b);
    getch();
    return 0;
}

每一次的结果是12。我不知道为什么结果是12,我想结果一定是4(或-4)。我的电脑是64位的,请给我解释一下。


解决方案

否您不能仅仅因为您知道sizeof int在您的情况下是4,就说结果一定是4

不存在符合标准的、 实现所需功能的便携方式(获取不属于任何数组的两个int变量的地址之差)。

在两个连续行中声明两个int变量并不意味着它们将被连续放置在内存中。很有可能顺序与您预期的不同。(在这种情况下,它是int a,b我在这里谈论的)。如果您希望int在内存中相邻,则数组(如int ab[2])是ISO C保证在所有实现上为您提供的唯一选项。(在大多数C实现中,您也可以使用struct,但理论上这不是完全可移植的。2)


如上所述,此代码将指针类型转换为调用实现定义的行为的int。还要注意,带符号整数溢出是UB,并且不能保证int可以保存特定系统中的地址。因此,intptr_t应该是一种安全的方法来避免UB,并通过减去单独对象的地址的整数值来获得仅由实现定义的结果。

前面提到的要点是,如果我们认为该体系结构实现了平面寻址(就像实际使用的几乎所有C实现一样),那么我们只需将指针指向intptr_t并减去它,就可以得到结果1。但正如它所说的那样-标准从未约束过这种特定的内存布局(它不要求体系结构是这样的)--更加健壮,适用于大量系统。无论说什么都是正确的,直到我们考虑到体系结构中没有平面地址空间的实现可能会有一些问题,需要它以复杂的方式访问元素。

注意:如果使用或不使用不同的优化标志(-O3-O2等)运行这段代码,您可能会得到所需的+4-4结果。这一定是特定于编译器的用例,它给出了这个结果。(很可能不是gcc)。


脚注

  1. 将对象地址转换为整数是一个两个阶段的过程:首先转换为void *,然后转换为类似intptr_t/uintptr_t的整数。要打印两个这样的整数的差值,请使用PRIdPTR/PRIuPTRintptr_tuintptr_t是可选类型,但从C99开始,非常常见。如果intptr_t/uintptr_t不可用,则强制转换为最广泛的可用类型并使用其匹配的说明符。

#include <inttypes.h>
// printf("%d", (int)&a - (int)&b);
printf("%" PRIdPTR, (intptr_t)(void*)&a - (intptr_t)(void*)&b);
// or pre-C99
printf("%ld", (long)(void*)&a - (long)(void*)&b);

  1. struct布局和字体大小:

    在实践中,struct intpair { int a,b; } ab;在主流实现上也会有连续的ab,但ISO C允许在结构布局中进行任意数量的填充。不过,它确实要求结构成员具有递增的地址,因此编译器可以填充结构,但不能对结构重新排序。(或C++中的类;规则相同)。

    因此,为了最大限度地减少填充(为了速度/缓存空间效率),从最大到最小对成员进行排序通常是个好主意,因为许多类型的对齐要求与其宽度相等。或者,如果要将较小的成员放在较大的成员之前,则将它们成对/四对地分组。请记住,许多实际实现在32/64位指针和/或32/64位long之间有所不同。例如,x86-64 Windows上的64位指针和32位long,但x86-64上的64/64其他一切。当然,纯ISO C只设置类型必须能够表示的值的最小范围,它们的最小sizeof1,但大多数现代CPU(以及它们的主流C实现)都已确定为32位int

    避免编写依赖于正确性假设的代码,但在考虑性能时牢记这一点很有用。

相关文章