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

C++ 计算机如何分配两个变量?我们如何计算两个变量之间的距离?,c++,c,cpu-architecture,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位的,请解释一下 不,你不能简单地说结果必

当我试图检查下面代码中两个变量之间的差异时,我发现了一些有趣的事情

#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

没有符合标准的, 一种可移植的方法,可以忽略两个不属于任何数组的int变量的地址之间的差异

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

正如前面指出的,这段代码正在类型化指向int的指针,int调用实现定义的行为。还要注意,有符号整数溢出是UB,并且不能保证int可以在特定系统中保存地址。因此,intptr_t应该是一种安全的方法,可以避免UB,并通过减去单独对象地址的整数值得到一个仅由实现定义的结果

一个很好的观点,如果我们考虑到在实际使用中,几乎所有的C实现都实现了平面寻址,那么我们可以简单地把指针转换成IpTrrt并将其减去得到结果1。但正如它所说的那样——标准从未约束过这种特殊的内存布局——它不要求体系结构是这样的——要更加健壮并适用于大量系统。任何我们所说的都是正确的,直到我们认为在一个没有平坦地址空间的体系结构中的实现可能有一些问题需要它去访问复杂的方式。 注意:如果您使用gcc运行这段代码,并使用或不使用不同的优化标志-O3、-O2等,则可能会得到所需的+4或-4结果。这必须是给出此结果的编译器特定案例。它很可能不是gcc

脚注

将对象地址转换为整数需要两个阶段:首先转换为void*,然后转换为整数,如intptr\u t/uintpttr\t。要打印两个这样的整数的差,请使用PRIdPTR/PRIuPTR。intptr\u t和uintptr\u t是可选类型,但自C99以来非常普遍。如果intptr\u t/uintptr\u t不可用,请强制转换为最宽的可用类型并使用其匹配的说明符。 结构布局和类型大小:

实际上,结构intpair{inta,b;}ab;在主流实现中也将有连续的a和b,但ISO C允许在结构布局中使用任意数量的填充。但是,它确实要求结构成员具有不断增加的地址,因此编译器可以填充结构,但不能对结构进行重新排序。或C++类;那里的规则是一样的

因此,为了最大限度地减少填充以提高速度/缓存空间效率,通常最好从最大到最小对成员进行排序,因为许多类型的对齐要求都等于它们的宽度。或者,如果要将较小的成员放置在较宽的成员之前,请将其分组为成对/四边形。请记住,许多实际实现在32/64位指针和/或32/64位长度之间存在差异。e、 g.在x86-64 Windows上为64位指针和32位长,但在x86-64上为64/64,其他一切都是如此。当然,纯ISO C只设置类型必须能够表示的值的最小范围,并且它们的最小sizeof为1,但是大多数现代CPU和主流C实现都采用32位int

避免编写依赖于正确性假设的代码,但在考虑性能时要记住这一点


不,你不能简单地说结果必须是4,因为你知道在你的例子中sizeof int是4

没有符合标准的, 一种可移植的方法,可以忽略两个不属于任何数组的int变量的地址之间的差异

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

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

一个很好的观点,如果我们考虑到在实际使用中,几乎所有的C实现都实现了平面寻址,那么我们可以简单地把指针转换成IpTrrt并将其减去得到结果1。但正如它所说的那样——标准从未约束过这种特殊的内存布局——它不要求体系结构是这样的——要更加健壮并适用于大量系统。任何我们所说的都是正确的,直到我们认为在一个没有平坦地址空间的体系结构中的实现可能有一些问题需要它去访问复杂的方式。 注意:如果您使用gcc运行这段代码,并使用或不使用不同的优化标志-O3、-O2等,则可能会得到所需的+4或-4结果。这必须是给出此结果的编译器特定案例。它很可能不是gcc

脚注

将对象地址转换为整数需要两个阶段:首先转换为void*,然后转换为整数,如intptr\u t/uintpttr\t。要打印两个这样的整数的差,请使用PRIdPTR/PRIuPTR。intptr\u t和uintptr\u t是可选类型,但自C99以来非常普遍。如果intptr\u t/uintptr\u t不可用,请强制转换为最宽的可用类型并使用其匹配的说明符。 结构布局和类型大小:

实际上,结构intpair{inta,b;}ab;在主流实现中也将有连续的a和b,但ISO C允许在结构布局中使用任意数量的填充。但是,它确实要求结构成员具有不断增加的地址,因此编译器可以填充结构,但不能对结构进行重新排序。或C++类;那里的规则是一样的

因此,为了最大限度地减少填充以提高速度/缓存空间效率,通常最好从最大到最小对成员进行排序,因为许多类型的对齐要求都等于它们的宽度。或者,如果要将较小的成员放置在较宽的成员之前,请将其分组为成对/四边形。请记住,许多实际实现在32/64位指针和/或32/64位长度之间存在差异。e、 g.在x86-64 Windows上为64位指针和32位长,但在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;

由于该标准没有为指向不相关对象的指针指定算法,因此减去此类指针容易导致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;

不同实体上的指针算法是UB>,不能使用标准C++来计算两个变量之间的距离,这些变量没有被分配为同一个数组的一部分。您的代码的结果是未定义的行为。我认为结果必须是4-为什么您认为是这种情况?我认为结果可能是4的重复是错误的假设。不同实体上的指针算法是UB>您无法计算未分配为同一数组一部分的两个变量之间的距离使用标准C++。您的代码的结果是未定义的行为。我认为结果必须是4-为什么您认为是这样?我认为结果可能是4的重复是一个错误的假设。@MatteoItalia.:对于平面寻址-是的,它是正确的。那么,这一冲突甚至不成立。但标准并没有要求它。@MatteoItalia:我想这不是一个法律上的错误答案。恐怕你的意图不清楚。如果是大约
我没有给出正确的答案——确实是这样。如果对你来说不是太难的话,我将非常感谢你的回复。@VănHợ我为早些时候给你带来的困惑道歉——因为我没有注意到演员阵容。该转换是由实现定义的。我前面的观点认为,我们没有我们在这里提到的架构约束。这是问题本身已经证明的——如果它们不是一个又一个,那么很明显,两者之间存在某种东西。现在的问题是:已知编译器不会浪费堆栈空间,那么其中有什么,为什么?填充以保持变量对齐?其他本地人?金丝雀旗?如果您阅读链接的副本,您将发现很有可能中间的空间用于缓冲区溢出保护。这是一个答案,我从中得到了一些东西,而不是一个焦点,为什么测量在技术上不是由C++保证的。@ CODReRoDoc:给定了结束时的GETCH,避免在从IDE内部运行它之后关闭终端,我的猜测是VC++在Windows上。当然,唯一能确定的方法是询问。@MatteoItalia.:对于平面寻址-是的,它是正确的。那么,这一冲突甚至不成立。但标准并没有要求它。@MatteoItalia:我想这不是一个法律上的错误答案。恐怕你的意图不清楚。如果它是关于发布一个正确的答案,那么它确实做到了。如果对你来说不是太难的话,我将非常感谢你的回复。@VănHợ我为早些时候给你带来的困惑道歉——因为我没有注意到演员阵容。该转换是由实现定义的。我前面的观点认为,我们没有我们在这里提到的架构约束。这是问题本身已经证明的——如果它们不是一个又一个,那么很明显,两者之间存在某种东西。现在的问题是:已知编译器不会浪费堆栈空间,那么其中有什么,为什么?填充以保持变量对齐?其他本地人?金丝雀旗?如果您阅读链接的副本,您将发现很有可能中间的空间用于缓冲区溢出保护。这是一个答案,我从中得到了一些东西,而不是一个焦点,为什么测量在技术上不是由C++保证的。@ CODReRoDoc:给定了结束时的GETCH,避免在从IDE内部运行它之后关闭终端,我的猜测是VC++在Windows上。当然,唯一确定的方法就是问。@VănHợpBùi您可以使用上述程序测试特定系统上的行为。使用%p打印int*是UB。减去不相关的指针就是UB。使用%d打印ptrdiff\t是UB的另一层。printf\n牵引地址:%lld\n,long int&b-long int&a;是另一个UB。不清楚为什么代码在这里没有长时间转换。这将是这个答案的最佳方法。@chux谢谢您的评论!。代码也相应地得到了改进。@VănHợpBùi您可以使用上述程序测试特定系统上的行为。使用%p打印int*是UB。减去不相关的指针就是UB。使用%d打印ptrdiff\t是UB的另一层。printf\n牵引地址:%lld\n,long int&b-long int&a;是另一个UB。不清楚为什么代码在这里没有长时间转换。这将是这个答案的最佳方法。@chux谢谢您的评论!。代码也相应地得到了改进。
Address of b 0x7ffc2d3cd2d4
Address of a 0x7ffc2d3cd2d0
Substructing pointers:  1

Subtracting addr:  4
Subtracting addr:  4
-4