我想知道为什么以下代码无法编译:
void foo_int(int *a) { }
void foo_long(long *a) { }
int main()
{
int i;
long l;
foo_long(&i);
foo_int(&l);
}
Run Code Online (Sandbox Code Playgroud)
我正在使用GCC,并且这两个调用都不能在C或C++中工作.由于它是一个32位系统,因此int和long都是带符号的32位整数(可以在编译时使用sizeof进行验证).
我问的原因是我有两个单独的头文件,都不在我的控制之下,而且有一个像:typedef unsigned long u32;和另一个:typedef unsigned int uint32_t;.声明基本上是兼容的,除非我在上面的代码片段中使用它们作为指针,我必须显式转换.
知道为什么会这样吗?
sys*_*USE 28
仅仅因为long并且int在您的特定编译器和硬件上都发生了32位,并不意味着它们在每种硬件和每个编译器上都将始终是32位.
C(和C++)被设计为在不同编译器和不同硬件之间可移植源.
Max*_*ert 18
我在这里可以看到两个不同的问题.
首先,在现代架构上,可以非常安全地假设指针的大小相同(也就是说,没有近/远指针;但指向成员函数的指针不是常规指针,可能是不同的大小); 在32位系统上,该大小通常为32位.C甚至可以自动转换void*为其他任何东西,因为毕竟指针实际上只是一个内存地址.然而,语言定义将各种指针区分为不同的类型.我相信这样做的原因是不同的类型可以有不同的对齐方式(void*规则是没有什么可以真正的类型void,所以如果你有一个指向void你的指针可能知道正确的类型,并且隐含地,正确的对齐(见注释) )
其次,正如其他人所指出的,longs和ints基本上是不同的类型,内置默认转换.标准要求longs至少与ints 一样大,但可能更大.在您的体系结构上,对齐可能是相同的,但在其他体系结构上也可能不同.
在您的情况下可以捏造,但它不便携.如果你没有#include正确的函数声明,而只是简单地向前声明它们应该神奇地工作,因为在你的情况下longs和ints兼容(假设没有签名问题;同样在C++中你需要extern "C"你的声明和实际函数实现,以便您不会得到链接错误).直到您切换到不同的编译器,或不同的操作系统,或不同的架构等.
例如,在C++中你可以这样做:
// in file lib.cc
#include <iostream>
extern "C" void foo_int(int* a)
{
std::cout << "foo_int " << *a << " at address " << a <<'\n';
}
extern "C" void foo_long(long* a)
{
std::cout << "foo_long " << *a << " at address " << a <<'\n';
}
Run Code Online (Sandbox Code Playgroud)
// In file main.cc
extern "C" void foo_int(long* a);
extern "C" void foo_long(int* a);
int main()
{
int i = 5;
long l = 10;
foo_long(&i);
foo_int(&l);
}
Run Code Online (Sandbox Code Playgroud)
(在C中,你将摆脱extern "C"并使用printf而不是cout).
使用GCC你会像这样编译:
$ g++ -c lib.cc -o lib.o
$ g++ main.cc lib.o
$ ./a.out
foo_long 5 at address 0x22cce4
foo_int 10 at address 0x22cce0
Run Code Online (Sandbox Code Playgroud)
注意 由于没有类型的对象void,void*因此只能指向其他类型的对象.并且编译器在将对象放在那里时就知道了真正的类型.并且编译器在分配对象时知道该类型的对齐方式.您可能不太了解对齐方式,但只有在返回原始类型时才能保证转换,在这种情况下,对齐和大小将相同.
但是有皱纹.首先,对象的打包在两个地方必须相同(不是基本类型的问题).另一方面,它是可以在任意存储点void*S,但程序员谁做,大概意识到自己在做什么.