当我在C语言中将long int赋值给int时会发生什么?

58

在最近的一次作业中,我被告知要使用long变量来存储结果,因为它可能是一个大数。

我决定检查一下,在我的系统上(intel core i5/64位windows 7/gnu gcc编译器),以下代码是否真的会对我产生影响:

printf("sizeof(char) => %d\n", sizeof(char));
printf("sizeof(short) => %d\n", sizeof(short));
printf("sizeof(short int) => %d\n", sizeof(short int));
printf("sizeof(int) => %d\n", sizeof(int));
printf("sizeof(long) => %d\n", sizeof(long));
printf("sizeof(long int) => %d\n", sizeof(long int));
printf("sizeof(long long) => %d\n", sizeof(long long));
printf("sizeof(long long int) => %d\n", sizeof(long long int));

生成以下输出:

sizeof(char) => 1
sizeof(short) => 2
sizeof(short int) => 2
sizeof(int) => 4
sizeof(long) => 4
sizeof(long int) => 4
sizeof(long long) => 8
sizeof(long long int) => 8

在我的系统上,intlong 是相同的,任何对于 int 太大而无法容纳的值,long 也会太大而无法容纳。
作业本身并不是问题所在。我想知道,在一个 int < long 的系统中,如何将 int 赋值给 long

我知道在这个主题上有许多相关的问题(链接1) (链接2) (链接3) (链接4) (链接5),但我觉得这些答案并没有给我完全理解该过程中可能发生的情况。

基本上,我正在尝试弄清楚以下问题:

  1. 在赋值之前,我应该把 long 转换成 int 吗?还是直接赋值会被认为没有害处,因为 long 只是一个修饰符而非不同的数据类型?
  2. long > int 的系统上会发生什么?结果会是未定义的(或不可预测的)还是会使变量的额外部分被省略?
  3. C语言中从 long 转换为 int 的转换是如何工作的?
  4. 如果我不使用强制类型转换,在C语言中从 long 赋值给 int 是如何工作的?

4
C语言有修饰符(volatile, const),但shortlongsignedunsigned不是修饰符,它们指定了独立的类型。 - Mooing Duck
小心。如果你看一下Linux AMD64系统,long类型是8个字节,而int类型是4个字节。请参考https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models了解一些常见的情况。 - user507577
@Rajesh:我认为这与操作系统或硬件无关。数据类型的大小是编译器做出的(主要是)任意决定。 - Mooing Duck
@MooingDuck 当一个平台的所有编译器都做同样的事情时,它就更或多或少地被解决了 :) - user507577
6
编译器所做的决定(更准确地说,由其作者做出的决定)受操作系统和硬件的强烈影响。许多系统都有规范整数类型大小的ABI;遵循它可以使来自不同编译器编译的代码相互混用。在某些情况下,向后兼容性是一个强大的影响因素,有时会导致在64位系统上出现32位的“long”等情况。 - Keith Thompson
@MooingDuck,感谢您对修饰符/类型问题的纠正! - Khaloymes
2个回答

50

该语言保证int至少为16位,long至少为32位,并且long可以表示至少与int相同的所有值。

如果将long值分配给int对象,则会进行隐式转换。不需要显式强制转换;它只会指定即将发生的相同转换。

在您的系统上,其中intlong具有相同的大小和范围,转换是微不足道的;它只是简单地复制值。

在长宽大于整型的系统上,如果值无法适应int,则转换的结果是实现定义的。(或者,在C99中,它可以引发实现定义的信号,但我不知道任何实际执行该操作的编译器。)通常会发生的是舍弃高位比特,但是您不应该依赖于此。(对于无符号类型,规则有所不同;将有符号或无符号整数转换为无符号类型的结果是定义良好的。)

如果您需要安全地long值分配给int对象,可以在执行分配之前检查它是否适合:

#include <limits.h> /* for INT_MIN, INT_MAX */

/* ... */

int i;
long li = /* whatever */

if (li >= INT_MIN && li <= INT_MAX) {
    i = li;
}
else {
    /* do something else? */
}

"某些其他的"细节将取决于您想要做什么。

一个修正:即使它们有相同的大小和表示,intlong 总是不同的类型。算术类型可以自由转换,因此这通常不会有任何区别,但是例如,int*long* 是不同且不兼容的类型;您不能将long*赋值给int*,反之亦然,而不使用显式(并且可能危险的)转换。

如果您发现自己需要将long值转换为int,那么您应该首先重新考虑一下您的代码设计。有时这样的转换是必要的,但更多的情况是,您分配给int的值在首次定义时应该是long类型。


首先,感谢您的回答!我假设(从谷歌搜索一下)为了使用INT_MIN和INT_MAX,我必须包含limits.h。有没有一种方法可以在不包括此头文件的情况下确定它们的大小?也许只是从sizeof中? - Khaloymes
@Khaloymes:是的,你需要 #include <limits.h>;我应该提到这一点。请注意,这些是边界而不是大小。可能有方法可以在不使用 <limits.h> 的情况下计算它们,但为什么要麻烦呢?毕竟,这就是 <limits.h> 的作用。 - Keith Thompson
那个实现定义的信号很有趣。特别是我在标准中找不到它仅适用于隐式转换的说明;如果实现确实采用了这种方式,那么通常使用强制转换作为“不要警告我,我知道自己在做什么”的方法将无法阻止行为,正如人们直觉地期望的那样。 - Alex Celeste
@Leushenko:是的,它适用于转换,无论是显式的(强制转换)还是隐式的。 - Keith Thompson
把高位位数舍去称为“截断”是否恰当?还是这个术语只适用于浮点转整数的情况? - Lakey
1
@Lakey:我本来会说不是,但C标准确实将其称为“截断”。(而且恰好,你的问题让我发现了标准中可能存在的错误。N1570 5.1.2.3p11,例2,说char c1, c2; /* ... */ c1 = c1 + c2;将“截断总和”,但如果char是有符号的,则结果是实现定义的。我想你仍然可以称之为“截断”。无论如何,示例都不是规范性的。) - Keith Thompson

3
一个 long 总是可以表示所有的 int 值。如果已有的值可以被你分配的变量类型所表示,则该值将会被保留。如果不能被表示,对于带符号的目标类型,结果形式上是未指定的,而对于无符号的目标类型,则被指定为原始值模 2n,其中 n 是值表示中的位数(这不一定是目标中的所有位)。在现代机器上,即使是有符号类型也会发生包装,这是因为现代机器使用二进制补码表示有符号整数,没有任何位用于表示“无效值”等 - 也就是说,所有位都用于表示值。
n 位值表示,任何整数值 x 被映射到 x+K*2n,其中整数常数 K 的选择使得结果在可能值的一半为负数的范围内。因此,例如,在 32 位 int 中,值 -7 被表示为位模式数字 -7+232 = 232-7,因此如果您将比特模式所代表的数字显示为无符号整数,则会获得一个相当大的数字。
这被称为二进制补码的原因是因为它对于二进制数字系统 - 即基数为 2 的数字系统 - 是有意义的。对于二进制数字系统,还有一种反码(注意撇号的位置)。类似地,在十进制数字系统中,存在十位补码和九位补码。使用 4 位十进制补码表示,你将把 -7 表示为 10000-7 = 9993。就这样了,真的。

2
C语言从C99开始要求有符号整数采用二进制补码、反码或原码表示。几乎所有现代系统都使用二进制补码。 - Keith Thompson
@KeithThompson C99是如何决定要求有符号整数表示为二进制补码还是表示为反码的?我的意思是,我能事先知道我的有符号整数将如何表示吗?在分配值后是否有改变表示的方法?另外,请告诉我这个评论是否太宽泛,应该转化为一个新问题。 - Khaloymes
@Khaloymes:不确定你的意思。这是标准中的明确要求;请参见第6.2.6.2节。您无法更改系统对有符号整数的操作方式;这通常由硬件确定。当然,您可以进行任何位操作,但如果您的系统使用二进制补码,除非您处理某些外部强制数据要求,否则很少有理由使用其他表示形式。 - Keith Thompson
@KeithThompson 这就是我想知道的,如果我能决定有符号整数的表示方式。从您的评论中我得出结论,这取决于硬件。 - Khaloymes
@Khaloymes:这取决于编译器的实现者,他们必须记录选择。这个决定几乎肯定是基于硬件支持的(这些天几乎肯定是二进制补码)。 - Keith Thompson

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