我一直在用 C 语言进行一些指针测试,我只是好奇函数参数的地址是否总是相差 4 个字节。
我尝试运行以下代码:
#include <stdio.h>
void func(long a, long b);
int main(void)
{
func(1, 2);
getchar();
return 0;
}
void func(long a, long b)
{
printf("%dn", (int)&b - (int)&a);
}
此代码似乎总是打印 4,无论 func 的参数类型是什么。 我只是想知道它是否总是 4,因为如果是这样,它对我正在尝试做的事情很有用(但如果不一定是 4,我想我可以将va_list用于我的函数或其他东西)。 所以:一定是4个字节吗?
绝对不是,在很多方面很难将它们全部计算出来。
首先,参数的内存布局根本不是由 C 语言指定的。 句点。 未指定。 因此,答案立即是否定的。
va_list
之所以存在,是因为需要能够浏览一系列变量参数,因为除此之外没有指定它。va_list
故意非常有限,因此它可以在堆栈形状与您的直觉不匹配的平台上工作。
它不能总是 4 的其他原因:
- 如果传递长度为 8 的对象怎么办?
- 如果编译器优化引用以实际指向另一帧中的对象,该怎么办?
- 如果编译器添加填充,也许是为了在 64 位边界上对齐 64 位数字怎么办?
- 如果堆栈以相反的方向构建(使得差异将是 -4 而不是 +4)怎么办
这样的例子不胜枚举。 C 不指定参数之间的相对地址。
正如其他答案正确说的那样:
不。
此外,即使试图确定地址是否相差 4 个字节,这取决于你如何做到这一点,也可能有未定义的行为,这意味着 C 标准没有说明你的程序做什么。
void func(long a, long b)
{
printf("%dn", (int)&b - (int)&a);
}
&a
和&b
是long*
类型的表达式。将指针转换为int
是合法的,但结果是实现定义的,并且"如果结果无法以整数类型表示,则行为未定义。结果不必在任何整数类型的值范围内。
指针很可能是 64 位,int
是 32 位,因此转换可能会丢失信息。
转换很可能会为您提供类型int
的值,但它们不一定有任何意义,它们的差异也不一定。
现在你可以直接减去指针值,结果是有符号整数类型ptrdiff_t
(与int
不同,它可能足够大,可以保存结果)。
printf("%tdn", &b - &a);
但是"当减去两个指针时,两个指针都应指向同一数组对象的元素,或者一个指针指向数组对象的最后一个元素;结果是两个数组元素的下标的差异。 指向不同对象的指针无法有意义地进行比较或减去。
综上所述,您使用的实现很可能具有相当简单的内存模型,并且指针值实际上表示为整体内存空间的索引。 比较&b
与&a
语言不允许,但检查这些值可以提供有关幕后发生的事情的一些见解 - 如果您正在跟踪错误,这可能特别有用。
您可以携带以下操作来检查地址:
printf("&a = %pn", (void*)&a);
printf("&b = %pn", (void*)&b);
您看到的减法 (4) 结果表明,系统上的类型long
可能是 4 字节(32 位)。我猜你在Windows上。它还建议了一些关于函数参数分配方式的信息——作为程序员,你几乎不应该关心这些东西,但无论如何都值得理解。
[...]我只是好奇函数参数的地址是否总是相差 4 个字节。
推理中最大的错误是认为参数根本不存在于内存中。
我在x86-64上运行此程序:
#include <stdio.h>
#include <stdint.h>
void func(long a, long b)
{
printf("%dn", (int)((intptr_t)&b - (intptr_t)&a));
}
int main(void)
{
func(1, 2);
}
并编译它gcc -O3
打印8
,证明你的猜测是绝对错误的。除了。。。当我在没有优化的情况下编译它时,它会打印出-8
.
X86-64 SYSV 调用约定表示,参数在寄存器中传递,而不是在内存中传递。a
和b
没有地址,直到你用&
获取他们的地址 - 然后编译器被抓到裤子,因为它欺骗了好像规则,它迅速拉起裤子并将它们塞到某个内存位置,以便他们可以获取他们的地址,但它在存储位置上绝不一致。