我想知道为什么以下代码无法编译:
void foo_int(int *a) { }
void foo_long(long *a) { }
int main()
{
int i;
long l;
foo_long(&i);
foo_int(&l);
}
我正在使用GCC,并且这两个调用都不能在C或C ++中工作。由于它是一个32位系统, int 和 long 都是带符号的32位整数(可以在编译时使用sizeof进行验证)。
我问的原因是我有两个单独的头文件,这两个文件都不在我的控制之下,而且有一个像:typedef unsigned long u32;
和另一个:typedef unsigned int uint32_t;
。声明基本上是兼容的,除非我在上面的代码片段中使用它们作为指针,我必须明确地转换。
知道为什么会这样吗?
答案 0 :(得分:28)
仅仅因为long
和int
在您的特定编译器和硬件上都是32位,并不意味着它们在每种硬件和每个编译器上都将始终都是32位
C(和C ++)被设计为在不同的编译器和不同的硬件之间可以移植源。
答案 1 :(得分:26)
...因为C ++标准将int和long定义为两个不同的类型,无论它们的值范围,表示等等。如果“基本上兼容”你的意思是可以相互转换,那么是。
答案 2 :(得分:18)
我可以在这里看到两个不同的问题。
首先,在现代架构上,假设指针大小相同(即没有近/远指针;但是指向成员函数的指针不是常规指针,可能是不同的大小),这是非常安全的。在32位系统上,该大小通常为32位。 C甚至可以自动将void*
转换为其他任何东西,因为毕竟指针实际上只是一个内存地址。然而,语言定义将各种指针区分为不同的类型。我认为这样做的原因是不同的类型可以有不同的对齐方式(void*
规则是没有任何东西可以真正属于void
类型,所以如果你有一个指向void
的指针你可能知道正确的类型,并且隐含地知道正确的对齐方式(参见注释))
其次,正如其他人所指出的那样,long
和ints
基本上是不同的类型,内置默认转换。标准要求long
s至少与int
一样大{1}} s,但可能更大。在您的体系结构上,对齐可能是相同的,但在其他体系结构上也可能不同。
可以在你的情况下捏造,但它不便携。如果你没有#include
正确的函数声明,而只是简单地向前声明它们应该神奇地工作,因为在你的情况下long
和int
是兼容的(假设没有签名问题;同样在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';
}
// 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);
}
(在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
注意由于没有void
类型的对象,void*
只能指向其他类型的对象。并且编译器在将对象放在那里时就知道了真正的类型。并且编译器在分配对象时知道该类型的对齐方式。您可能并不真正知道对齐方式,但只有在返回原始类型时才能保证转换,在这种情况下,对齐和大小将相同。
但是有皱纹。首先,对象的打包在两个地方必须相同(不是基本类型的问题)。另一方面, 可能指向具有void*
s的任意内存,但执行此操作的程序员可能会意识到他们正在做的事情。
答案 3 :(得分:7)
long
和int
是两种不同的类型,即使它们的大小相同。如果某些编译器对它们的处理方式相同,那么c ++模板世界会产生巨大的后果。
答案 4 :(得分:4)
由于我不特别喜欢到目前为止给出的任何答案,所以我采用了C ++标准:
4.7积分转换[conv.integral]
1整数类型的右值可以是 转换为另一个的右值 整数类型。一个价值的左右 枚举类型可以转换为 整数类型的右值。
这表示允许将一个整数隐式转换为另一个整数,因此这两种类型(因为它们的大小相同)可以作为rvalues互换。
4.10指针转换[conv.ptr]
1一个整数常量表达式 ( expr.const )整数类型的右值 评估为零(称为null 指针常量)可以转换为指针类型。该 result是一个值(称为null) 该类型的指针值) 可以与每个指针区分开来 对象或功能。两个空 相同类型的指针值 比较平等。一个转换 null指针常量指针 到cv-qualified类型是单一的 转换,而不是a的顺序 指针转换后跟 资格转换 ( conv.qual )。
2类型“指向cv T的指针”的右值 其中T是对象类型,可以 转换为类型的右值 “指向cv void的指针。”结果 将“指向cv T的指针”转换为 “指向cv void的指针”指向 开始存储位置在哪里 类型T的对象,如 如果对象是派生最多的 类型为T的对象( intro.object ) (即,不是基类子对象)。
3类型“指向cv D的指针”的右值 其中D是类类型,可以 转换为类型的右值 “指向cv B的指针”,其中B是基数 D.的类( class.derived ) 如果B是无法访问的 ( class.access )或含糊不清 ( class.member.lookup )基类 D,一个需要这个的程序 转换是不正确的。结果 转换是一个指针 的基类子对象 派生类对象。 null 指针值转换为null 目标类型的指针值。
只允许隐式转换:
因此即使底层机器类型相同,也不允许在两种类型之间隐式转换。
答案 5 :(得分:2)
这是因为在某些平台上,long和int的大小不同。
16 bit: long=32bits int=16bits 32bit: long=32bits int=32bits 64bit(ILP64): long=64bits int=64bits 64bit(LP64): long=64bits int=32bits 64bit(LLP64): (what windows uses for whatever reason) long long=64bits long=32bits int=32bits
另外,更令人困惑的是,虽然你必须强制转换为两种类型之间的交互,但你不能像这样做函数重载,好像它们真的是两种不同的类型
long foo(int bar);
int foo(int bar);
答案 6 :(得分:2)
int *和long *是不同的类型,不一定相同。在每个真正的实现中,我认为它们都是,但这既不是在这里也不是符合标准的编译器。
我认为它是早期的PDP机器之一,其中char *大于int *。原因是该架构上的整数(36位)奇怪。因此系统会将多个9位字符打包成一个int,因此char *包含格式为(int *,int内部的offset)的地址。 **
该标准指定所有指针都可以表示为void *,并且暗示char *必须与void *相同,但是没有特别要求其他指针类型可以转换。
**我无法找到对此的引用,因此这可能是一个理论(但仍然有效)的例子,而不是实际的实现。C++ FAQ Lite
答案 7 :(得分:1)
int和long被定义为不同的类型,因此您可以编程可移植。
答案 8 :(得分:1)
我认为这些答案中的任何一个都不会受到追捧。
答案是:类型之间的有效转换并不意味着指针之间的有效转换。这很有道理,对吗?您需要以下代码进行编译
char a = 12;
int b = a;
但是让这段代码编译将是灾难的一个方法:
void foo(int* x) { x = 0x7f8f9faf; }
// ...
char a = 12;
foo(&a);
因为long
和int
之间存在转换并不意味着应该有指针转换。但是,您可能会抗议,long
和int
在您的编译器上具有完全相同的表示形式!你是对的,但这并没有改变这样的事实:就标准和编译器而言,它们是不同的类型。除非存在继承关系,否则不能隐式地在指向类型的指针之间进行转换。
此外,更一般地说,虽然C ++可能会根据不是基于int
等大的本地定义而改变它是否有效,但它不会改变它是否在语法上正确。完全折叠long
和int
之间的区别将会做到这一点。
答案 9 :(得分:0)
您不需要在每次通话时手动编写显式强制转换。一个简单的宏就可以做到:
#include <stdio.h>
void foo_int( int *a ){ printf("a: %i\n", *a ); }
#define foo_int(a) foo_int( (int*)a )
int main(){
long l = 12;
foo_int( &l ); // no warning, runs as expected
foo_int( l ); // no warning, segmentation fault
}