为什么“long *”和“int *”在32位代码中不兼容?

时间:2009-09-22 17:14:50

标签: c++ c

我想知道为什么以下代码无法编译:

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;。声明基本上是兼容的,除非我在上面的代码片段中使用它们作为指针,我必须明确地转换。

知道为什么会这样吗?

10 个答案:

答案 0 :(得分:28)

仅仅因为longint在您的特定编译器和硬件上都是32位,并不意味着它们在每种硬件和每个编译器上都将始终都是32位

C(和C ++)被设计为在不同的编译器和不同的硬件之间可以移植源。

答案 1 :(得分:26)

...因为C ++标准将int和long定义为两个不同的类型,无论它们的值范围,表示等等。如果“基本上兼容”你的意思是可以相互转换,那么是。

答案 2 :(得分:18)

我可以在这里看到两个不同的问题。

首先,在现代架构上,假设指针大小相同(即没有近/远指针;但是指向成员函数的指针不是常规指针,可能是不同的大小),这是非常安全的。在32位系统上,该大小通常为32位。 C甚至可以自动将void*转换为其他任何东西,因为毕竟指针实际上只是一个内存地址。然而,语言定义将各种指针区分为不同的类型。我认为这样做的原因是不同的类型可以有不同的对齐方式(void*规则是没有任何东西可以真正属于void类型,所以如果你有一个指向void的指针你可能知道正确的类型,并且隐含地知道正确的对齐方式(参见注释))

其次,正如其他人所指出的那样,longints基本上是不同的类型,内置默认转换。标准要求long s至少与int一样大{1}} s,但可能更大。在您的体系结构上,对齐可能是相同的,但在其他体系结构上也可能不同。

可以在你的情况下捏造,但它不便携。如果你没有#include正确的函数声明,而只是简单地向前声明它们应该神奇地工作,因为在你的情况下longint是兼容的(假设没有签名问题;同样在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)

longint是两种不同的类型,即使它们的大小相同。如果某些编译器对它们的处理方式相同,那么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   目标类型的指针值。

只允许隐式转换:

  • 0到任何指针类型(使其成为空指针)
  • 任何指向void *的指针类型(正确的cv-qualified)
  • 派生指向基指针(正确cv限定)的指针

因此即使底层机器类型相同,也不允许在两种类型之间隐式转换。

答案 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);

因为longint之间存在转换并不意味着应该有指针转换。但是,您可能会抗议,longint在您的编译器上具有完全相同的表示形式!你是对的,但这并没有改变这样的事实:就标准和编译器而言,它们是不同的类型。除非存在继承关系,否则不能隐式地在指向类型的指针之间进行转换。

此外,更一般地说,虽然C ++可能会根据不是基于int等大的本地定义而改变它是否有效,但它不会改变它是否在语法上正确。完全折叠longint之间的区别将会做到这一点。

答案 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
}