计算机如何分配两个变量,我们如何计算两个变量之间的距离?
How does computer allocate two variables and how can we calculate the distance between two variables?
当我试图检查两个变量之间的差异时,我发现了一些有趣的东西(您可以在下面的代码中看到)
#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位的,请解释一下。
不,你不可能说结果一定是 4
,仅仅因为你知道 sizeof int
在你的情况下是 4
。
没有standard-compliant,
做你正在寻找的事情的便携方式(获取两个 int
变量的地址之间的差异,而不是任何数组的一部分)。
在连续两行中声明两个 int
变量并不意味着它们将被连续放置在内存中。排序很可能与您预期的不同。 (在这种情况下它是 int a,b
我在这里谈论)。如果您希望 int
在内存中相邻,则数组(如 int ab[2]
)是 ISO C 保证在所有实现中都会为您提供的唯一选项。 (在大多数 C 实现中,您也可以使用 struct
,但理论上不能完全移植。2)
正如所指出的那样,此代码将指针转换为指向 int
的指针,它调用实现定义的行为。另请注意,有符号整数溢出是 UB,并且不能保证 int
可以保存特定系统中的地址。因此 intptr_t
应该是避免 UB 的安全方法,并且通过减去单独对象的地址的整数值仅得到 implementation-defined 结果。
前面提到的好的一点是,如果我们认为架构实现了平面寻址(就像实际使用中的几乎每个 C 实现一样),那么我们可以简单地将指针转换为 intptr_t
并减去它以获得结果1。但正如它所说的那样——标准从不限制这种特定的内存布局(它不要求架构是这样的)——更加健壮并适用于大量系统。在我们考虑到架构 没有 平面地址 space 的实现可能存在一些问题需要它以复杂的方式访问元素之前,无论说什么都是正确的。
注意:如果您 运行 这段带有 gcc
的代码有或没有不同的优化标志(-O3
、-O2
等),您很可能会得到 +4
或 -4
的预期结果。这一定是为您提供此结果的编译器特定情况。 (很可能不是 gcc
)。
脚注
- 将对象地址转换为整数是一个 2 阶段过程:首先转换为
void *
,然后转换为 intptr_t/uintptr_t
之类的整数。要打印两个这样的整数的差值,请使用 PRIdPTR/PRIuPTR
。 intptr_t
和 uintptr_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);
struct
布局和字体大小:
实际上,struct intpair { int a,b; } ab;
在主流实现中也会有连续的 a
和 b
,但 ISO C 允许在结构布局中任意数量的填充。不过,它确实要求结构成员的地址递增,因此编译可以填充但不会重新排序您的结构。 (或 C++ 中的 类;那里的规则相同)。
因此为了最小化填充(为了速度/缓存 space 效率),将成员从最大到最小排序通常是个好主意,因为许多类型的对齐要求等于它们的宽度。或者,如果您想将较小的成员放在较宽的成员之前,则将较小的成员成对/四边形分组。请记住,许多实际实现在 32 / 64 位指针和/或 32 / 64 位 long
之间有所不同。例如x86-64 Windows 上的 64 位指针和 32 位 long
,但 x86-64 上的所有其他内容都是 64/64。当然,纯 ISO C 只设置类型必须能够表示的最小值范围,并且它们的最小值 sizeof
是 1
,但是大多数现代 CPU(以及它们的主流 C 实现)已经确定了32 位 int
.
避免编写依赖于正确性假设的代码,但在考虑性能时牢记这一点很有用。
由于标准没有指定指向不相关对象的指针的算法,因此减去此类指针很容易出现 UB,并且它取决于实现。
由于它依赖于实现,因此不能指望结果。
通常,根据经验,编译器会在程序堆栈上分配两个彼此靠近(大小并排)的整数。在这种情况下,对于具有平面内存架构的系统,减去地址将得到 int
的大小。
这是一个测试,用于检查您的程序可以为您提供什么:
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
int main() {
int a, b;
// Print address of the variables of a and b
printf("Address of b %p\n", (void *)&b);
printf("Address of a %p\n", (void *)&a);
// THIS IS PRONE TO UB since pointers `&a` and '&b' not related to each other:
printf("Substructing pointers: %lld\n", &b - &a );
// Now we substract addresses:
// Get the distance in memory on any architecture with flat addressing.
printf("\nSubtracting addr: %lld\n", (long long int)&b - (long long int)&a);
printf("Subtracting addr: %lld\n", (__intptr_t)(void *)&b - (__intptr_t)(void *)&a);
printf("%" PRIdPTR, (intptr_t)(void*)&a - (intptr_t)(void*)&b);
return 0;
}
输出:
Address of b 0x7ffc2d3cd2d4
Address of a 0x7ffc2d3cd2d0
Substructing pointers: 1
Subtracting addr: 4
Subtracting addr: 4
-4
当我试图检查两个变量之间的差异时,我发现了一些有趣的东西(您可以在下面的代码中看到)
#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位的,请解释一下。
不,你不可能说结果一定是 4
,仅仅因为你知道 sizeof int
在你的情况下是 4
。
没有standard-compliant,
做你正在寻找的事情的便携方式(获取两个 int
变量的地址之间的差异,而不是任何数组的一部分)。
在连续两行中声明两个 int
变量并不意味着它们将被连续放置在内存中。排序很可能与您预期的不同。 (在这种情况下它是 int a,b
我在这里谈论)。如果您希望 int
在内存中相邻,则数组(如 int ab[2]
)是 ISO C 保证在所有实现中都会为您提供的唯一选项。 (在大多数 C 实现中,您也可以使用 struct
,但理论上不能完全移植。2)
正如所指出的那样,此代码将指针转换为指向 int
的指针,它调用实现定义的行为。另请注意,有符号整数溢出是 UB,并且不能保证 int
可以保存特定系统中的地址。因此 intptr_t
应该是避免 UB 的安全方法,并且通过减去单独对象的地址的整数值仅得到 implementation-defined 结果。
前面提到的好的一点是,如果我们认为架构实现了平面寻址(就像实际使用中的几乎每个 C 实现一样),那么我们可以简单地将指针转换为 intptr_t
并减去它以获得结果1。但正如它所说的那样——标准从不限制这种特定的内存布局(它不要求架构是这样的)——更加健壮并适用于大量系统。在我们考虑到架构 没有 平面地址 space 的实现可能存在一些问题需要它以复杂的方式访问元素之前,无论说什么都是正确的。
注意:如果您 运行 这段带有 gcc
的代码有或没有不同的优化标志(-O3
、-O2
等),您很可能会得到 +4
或 -4
的预期结果。这一定是为您提供此结果的编译器特定情况。 (很可能不是 gcc
)。
脚注
- 将对象地址转换为整数是一个 2 阶段过程:首先转换为
void *
,然后转换为intptr_t/uintptr_t
之类的整数。要打印两个这样的整数的差值,请使用PRIdPTR/PRIuPTR
。intptr_t
和uintptr_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);
struct
布局和字体大小:实际上,
struct intpair { int a,b; } ab;
在主流实现中也会有连续的a
和b
,但 ISO C 允许在结构布局中任意数量的填充。不过,它确实要求结构成员的地址递增,因此编译可以填充但不会重新排序您的结构。 (或 C++ 中的 类;那里的规则相同)。因此为了最小化填充(为了速度/缓存 space 效率),将成员从最大到最小排序通常是个好主意,因为许多类型的对齐要求等于它们的宽度。或者,如果您想将较小的成员放在较宽的成员之前,则将较小的成员成对/四边形分组。请记住,许多实际实现在 32 / 64 位指针和/或 32 / 64 位
long
之间有所不同。例如x86-64 Windows 上的 64 位指针和 32 位long
,但 x86-64 上的所有其他内容都是 64/64。当然,纯 ISO C 只设置类型必须能够表示的最小值范围,并且它们的最小值sizeof
是1
,但是大多数现代 CPU(以及它们的主流 C 实现)已经确定了32 位int
.避免编写依赖于正确性假设的代码,但在考虑性能时牢记这一点很有用。
由于标准没有指定指向不相关对象的指针的算法,因此减去此类指针很容易出现 UB,并且它取决于实现。
由于它依赖于实现,因此不能指望结果。
通常,根据经验,编译器会在程序堆栈上分配两个彼此靠近(大小并排)的整数。在这种情况下,对于具有平面内存架构的系统,减去地址将得到 int
的大小。
这是一个测试,用于检查您的程序可以为您提供什么:
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
int main() {
int a, b;
// Print address of the variables of a and b
printf("Address of b %p\n", (void *)&b);
printf("Address of a %p\n", (void *)&a);
// THIS IS PRONE TO UB since pointers `&a` and '&b' not related to each other:
printf("Substructing pointers: %lld\n", &b - &a );
// Now we substract addresses:
// Get the distance in memory on any architecture with flat addressing.
printf("\nSubtracting addr: %lld\n", (long long int)&b - (long long int)&a);
printf("Subtracting addr: %lld\n", (__intptr_t)(void *)&b - (__intptr_t)(void *)&a);
printf("%" PRIdPTR, (intptr_t)(void*)&a - (intptr_t)(void*)&b);
return 0;
}
输出:
Address of b 0x7ffc2d3cd2d4
Address of a 0x7ffc2d3cd2d0
Substructing pointers: 1
Subtracting addr: 4
Subtracting addr: 4
-4