如果在某个平台上“long”和“int”的大小相同 - 那么“long”和“int”在任何方面有所不同吗?

50
如果一个平台上的long intint表示相同,它们严格相同吗? 根据C标准,这些类型在平台上是否以任何方式表现出不同行为?例如,这样做总是有效的:
int int_var;
long long_var;

void long_bar(long *l);
void int_bar(int *i);

void foo() 
{
    long_bar(&int_var); /* Always OK? */
    int_bar(&long_var);
}

如果short和int恰好具有相同的表示方式,那么同样的问题也适用于它们。

在讨论如何为嵌入式C89编译器定义类似于int32_t的typedef时,问题就出现了,即使用intlong,它是否重要。


24
不同的类型是不同的,即使它们的位表示相同。你正在做的会破坏严格别名规则 - Some programmer dude
2
你是从语言律师的角度提出问题的吗(那么请阅读n1570或更好的文档...),还是你关心实践 - 那么你使用的编译器和目标处理器是什么?如果你的编译器是GCC,请使用gcc -Wall -Wextra - Basile Starynkevitch
9
即使它们的大小相同,由于C允许类型具有陷阱表示和填充位,它们仍然可能具有不同的范围。例如,int可以将0x80000000作为陷阱,但long可以正常存储它。或者,int可以有2个填充位,而long只有1个。 - phuclv
3
另一个例子是 charsigned charunsigned char 是 3 种不同的类型,它们的大小相同,但其范围也不同于 _Bool,后者通常也具有相同的大小。 - phuclv
1
使用GCC,您可以编写自己的GCC插件来改变其行为。而且,GCC和Clang都是开源编译器,您可以对它们进行改进。 - Basile Starynkevitch
显示剩余8条评论
3个回答

41

它们不是兼容类型,在一个简单的例子中可以看到:

int* iptr;
long* lptr = iptr; // compiler error here

这主要与指向这些类型的指针有关。同样,还有“严格别名规则”,使得该代码行为未定义:

int i;
long* lptr = (long*)&i;
*lptr = ...;  // undefined behavior

另一个微妙的问题是隐式提升。如果您有some_int + some_long,那么该表达式的结果类型为long。或者,在任一参数为无符号时,为unsigned long。这是由于通过通常的算术转换进行整数提升,详见隐式类型提升规则

大多数情况下都没有什么问题,但诸如此类的代码将失败:_Generic(some_int + some_long, int: stuff() ),因为表达式中没有long条款。

一般来说,在不同类型之间赋值时,不应该出现任何问题。对于uint32_t,它对应于哪种类型并不重要,因为您应该将uint32_t视为单独的类型。我会选择long以兼容小型微控制器,其中typedef unsigned int uint32_t;会出错。(当然,用于带符号等效项的typedef signed long int32_t;。)


@chux-ReinstateMonica:在_Generic(some_int + some_long, int: stuff())中没有long条目。 - user2357112
@chux-ReinstateMonica 有什么关系?在小型MCU上,unsigned int是16位。因此,如果您选择unsigned int来作为uint32_t,那么您就定义了一个带有16位的uint32_t - Lundin
我可能有点迟钝,但是——你的“隐式提升”示例似乎并不是关于隐式提升的;你只需使用 _Generic(some_long, int: stuff() )_Generic(some_int, long: stuff() ) 就会遇到相同的问题。 - ruakh
@ruakh 的意思是 _Generic(some_int + some_int, int: stuff() ) 是可以工作的。通过在一个受到通常算术转换影响的表达式中引入 long,我改变了类型为 long,即使 long 恰好与 int 大小相同。 - Lundin
同样地,考虑 _Generic( ... , int: stuff(), int32_t: stuff)_Generic( ... , long: stuff(), int: stuff)。当 int32_t 实际上与 int 相同时,前者将产生编译器错误。但后者不会,因为它们是相同大小的不同类型。 - Lundin
@Lundin:是的,完全正确。重要的一点是_Generic将它们视为不同的类型。如果它们不是不同的类型,那么关于隐式提升的部分就毫无意义了,并且本身没有任何有趣的后果(或者如果有的话,您还没有提到)。 - ruakh

15

longint类型的级别不同。类型long的级别高于类型int的级别。因此,在二进制表达式中,如果使用了一个long类型的对象和一个int类型的对象,则后者总是转换为long类型。

比较以下代码片段。

int x = 0;
unsigned int y = 0;

表达式x+y的类型是unsigned int.

long x = 0;
unsigned int y = 0;

表达式 x + y 的类型是 unsigned long(由于通常算术转换),前提条件是 sizeof( int ) 等于 sizeof( long)

这在允许函数重载的 C++ 中比在 C 中更为重要。

在 C 中,例如当您使用 i/o 函数(如 printf)指定正确的转换说明符时,您必须考虑到这一点。


5
即使在longint表示相同的平台上,标准也允许编译器故意忽略将值存储到long*可能会影响int*的值,反之亦然。例如:
#include <stdint.h>

void store_to_int32(void *p, int index)
{
    ((int32_t*)p)[index] = 2;
}
int array1[10];
int test1(int index)
{
    array1[0] = 1;
    store_to_int32(array1, index);
    return array1[0];
}
long array2[10];
long test2(int index)
{
    array2[0] = 1;
    store_to_int32(array2, index);
    return array2[0];
}

32位ARM版本的gcc将把 int32_t 视为与 long 同义词,并忽略将 array1 的地址传递给 store_to_int32 可能会导致该数组的第一个元素被写入的可能性。而32位版本的clang将把 int32_t 视为与 int 同义词,并忽略将 array2 的地址传递给 store_to_int32 可能会导致该数组第一个元素被写入的可能性。
确切地说,标准中没有任何内容禁止编译器以这种方式行事,但我认为标准未能禁止这种盲目行为是因为“某些东西愚蠢程度越高,就越不需要禁止它”的原则所导致的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接