如果平台上“long”和“int”的大小相同 - “long”和“int”有啥不同吗?

Posted

技术标签:

【中文标题】如果平台上“long”和“int”的大小相同 - “long”和“int”有啥不同吗?【英文标题】:If the size of "long" and "int" are the same on a platform - are "long" and "int" different in any way?如果平台上“long”和“int”的大小相同 - “long”和“int”有什么不同吗? 【发布时间】:2021-06-25 06:10:53 【问题描述】:

如果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,如果它们恰好是相同的表示。

在讨论如何为没有 stdint.h 的嵌入式 C89 编译器定义类似 int32_t 的 typedef 时出现了问题,即作为 intlong 以及是否重要。

【问题讨论】:

不同的类型是不同的,即使它们的按位表示是相同的。你在做什么打破了严格的别名 您是从语言律师的角度提出问题(然后阅读n1570 或更好......)还是您关心在实践中 - 然后使用什么编译器和目标处理器?如果您的编译器是GCC,则将其用作gcc -Wall -Wextra 即使它们的大小相同,它们仍可能具有不同的范围,因为 C 允许类型具有陷阱表示和填充位。例如int 可以将 0x80000000 作为陷阱,但long 可以正常存储它。或者 int 可以有 2 个填充位,但 long 只有 1 个 另一个例子是charsigned charunsigned char 是 3 种不同的类型,它们的大小相同,但它们的范围也不同于 _Bool_Bool 是另一种通常具有相同大小的类型 使用 GCC,您可以编写自己的 GCC 插件来更改其行为。 GCC 和 Clang 都是开源编译器,你可以改进 【参考方案1】:

即使在longint 具有相同表示的平台上,标准也允许编译器故意忽略将值存储到long* 的行为可能会影响@ 值的可能性987654324@ 或反之亦然。给定类似的东西:

#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 可能导致该数组的第一个元素被写入的可能性。

可以肯定的是,标准中的任何内容都不会禁止编译器以这种方式行事,但我认为标准未能禁止这种盲目性源于“越愚蠢,就越不需要禁止它”的原则”。

【讨论】:

【参考方案2】:

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

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

所以在处理指向这些类型的指针时最重要的是。同样,有一个“严格的别名规则”使这段代码行为未定义:

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

另一个微妙的问题是隐式提升。如果您有some_int + some_long,则该表达式的结果类型为long。或者,如果任一参数未签名,unsigned long。这是因为通过通常的算术转换进行整数提升,请参阅Implicit type promotion rules。 大多数情况下应该没关系,但是这样的代码会失败:_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 条目。 @chux-ReinstateMonica 签名与任何事情有什么关系? unsigned int 在小型 MCU 上为 16 位。因此,如果您为 uint32_t 选择 unsigned int,您将定义一个 16 位的 uint32_t 我可能很迟钝,但是——您的“隐式提升”示例似乎并不是关于隐式提升;只需 _Generic(some_long, int: stuff() )_Generic(some_int, long: stuff() ) 就会遇到同样的问题。 @ruakh 关键是_Generic(some_int + some_int, int: stuff() ) 会起作用。通过在进行常规算术转换的表达式中引入long,我将类型更改为long,即使long 恰好与int 具有相同的大小。 类似地,考虑_Generic( ... , int: stuff(), int32_t: stuff)_Generic( ... , long: stuff(), int: stuff)。当int32_t 实际上与int 的类型相同时,前者会产生编译器错误。但后者不会,因为它们是相同大小的独特类型。【参考方案3】:

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

比较下面的代码sn-ps。

int x = 0;
unsigned int y = 0;

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

long x = 0;
unsigned int y = 0;

如果sizeof( int ) 等于sizeof( long),则表达式x + y 的类型是unsigned long(由于通常的算术转换)。

这在 C++ 中比在允许函数重载的 C 中非常重要。

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

【讨论】:

以上是关于如果平台上“long”和“int”的大小相同 - “long”和“int”有啥不同吗?的主要内容,如果未能解决你的问题,请参考以下文章

C#中long和int的区别?

java中long型怎么比较大小

short型与int型的区别

java long型计算问题

如何确保跨平台的浮点大小相同?

在 64 位 Windows 上 long 的位大小是多少?