无符号和签名扩展

时间:2016-05-02 11:13:19

标签: c language-lawyer unsigned

有人可以向我解释以下代码输出:

void myprint(unsigned long a)
{
    printf("Input is %lx\n", a);
}
int main()
{
    myprint(1 << 31);
    myprint(0x80000000);
}

输出gcc main.c

Input is ffffffff80000000
Input is 80000000

为什么(1 << 31)被视为已签名且0x80000000被视为未签名?

5 个答案:

答案 0 :(得分:13)

在C中,表达式的结果取决于操作数(或某些操作数)的类型。特别是,1int(已签名),因此1 << n也是int

0x80000000的类型(包括签名)由规则here决定,它取决于系统上int和其他整数类型的大小,没有指明。选择一种类型,使0x80000000(一个大的正数)在该类型的范围内。

如果您有任何误解:文字0x80000000是一个大的正数。人们有时会错误地将其等同于负数,将值与表示混合在一起。

在您的问题中,您说“为什么0x80000000被视为无符号?”。但是,您的代码实际上并不依赖于0x80000000的签名。你用它做的唯一事情是将它传递给带有unsigned long参数的函数。因此,无论是签名还是未签名都无关紧要;传递给转换时,它会转换为具有相同值的unsigned long。 (由于0x80000000unsigned long的最小保证范围内,因此不可能超出范围。

所以,那是0x80000000处理的。那么1 << 31呢?如果您的系统具有32位int(或更窄),则由于带符号的算术溢出而导致undefined behaviour。 (Link to further reading)。如果您的系统具有较大的整数,那么这将产生与0x80000000行相同的输出。

如果您改为使用1u << 31,并且您有32位整数,则没有未定义的行为,并且您可以保证看到程序输出80000000两次。

由于您的输出不是80000000,因此我们可以断定您的系统具有32位(或更窄)的int,并且您的程序实际上会导致未定义的行为。如果0x80000000为32位,则unsigned int的类型为int,否则为unsigned long

答案 1 :(得分:6)

  

为什么(1 << 31)被视为已签名且0x80000000被视为未签名?

来自 6.5.7比特移位运算符的C11规范:

  

3对每个操作数执行整数提升。结果的类型   升级后的左操作数。 [...]
  4 E1的结果&lt;&lt; E2是E1左移E2位位置;腾空   位用零填充。如果E1具有无符号类型,则值为   结果是E1×2 E2 ,比最大值减少一个模数   在结果类型中可表示。如果E1有签名类型和   非负值,E1×2 E2 在结果类型中可表示,   那就是结果价值; 否则,行为未定义

所以,因为1int(来自下一段中提到的第6.4.4.1节),1 << 31也是int,其值不太好在int小于或等于32位的系统上定义。 (甚至可能陷阱)

来自 6.4.4.1整数常量

  

3十进制常数以非零数字开头,由a组成   十进制数字序列。八进制常量由前缀0组成   可选地后跟一个数字0到7的序列。一个   十六进制常量由前缀0x或0X后跟a组成   十进制数字的序列和字母a(或A)到f(或   F)分别为10到15的值。

  

5整数常量的类型是对应的第一个   列表中可以表示其值

Suffix   |           decimal Constant         |   Hex Constant
---------+------------------------------------+---------------------------
none     |       int                          |  int
         |       int                          |  unsigned int
         |                                    |  long int
         |       long int                     |  unsigned long int
         |                                    |  long long int
         |       long long int                |  unsigned long long int
---------+------------------------------------+---------------------------
u or U   |       unsigned int                 |  unsigned int
[...]    |       [...]                        |  [...]

因此,0x80000000位为32位或更小位int32位或更大unsigned int的系统上的unsigned intdatetime

答案 2 :(得分:2)

您显然使用的是32位public class YASGP { public static void main(String args[]) throws IOException { ServerSocket server; try{ server = new ServerSocket(5559); System.out.println("Listening for connection on port 5559 ...."); while (true) { Socket clientSocket = server.accept(); InputStreamReader isr = new InputStreamReader(clientSocket.getInputStream()); BufferedReader reader = new BufferedReader(isr); String line = reader.readLine(); new Thread(new WorkerRunnable(clientSocket)).start(); while (!line.isEmpty()) { System.out.println("----- " + line); if (!line.contains("OPTIONS")) { // System.out.println("Non c'è nulla!!!"); } else { timeS = line.substring(line.indexOf("timeS=") + 6, line.indexOf("&url")); url = line.substring(line.indexOf("url=") + 4, line.lastIndexOf("&param")); param = line.substring(line.indexOf("&param=") + 7, line.indexOf("HTTP")); } line = reader.readLine(); } } } }catch (IOException e) { System.out.println("Could not listen on port: 4001"); } } private static class RequestHandlingClass { public RequestHandlingClass(Socket clientSocket) { } } } int的系统。

unsigned int适合1,因此它是intsigned int则不是。对于十进制常量,将使用下一个较大的有符号类型,它可以保存该值,对于十六进制和八进制常量,首先使用相应的无符号类型(如果适合)。这是因为无论如何它们通常都是无符号的。有关完整的值/类型矩阵,请参阅C标准6.4.4.1p5

对于有符号整数,通过更改符号的左移是未定义的行为。这意味着所有赌注都已关闭,因为您超出了语言规范。

说,以下是对结果的解释:

  • 0x80000000显然是您系统上的64位。
  • longint转移到符号位,就像您预期的那样。
  • 结果为负1
  • 否定int转换为ints,使得2的补码表示不需要任何操作(只需重新解释位模式)
  • 当您使用64位unsigned时,该符号将扩展为unsigned long参数的高位。

如何避免它:

  • 移位时始终使用无符号整数(例如,在适当的位置将myprint后缀附加到整数常量,此处为:U1U)。
  • 使用比0x1U更小的类型时,请注意标准整数转换。
  • 通常,如果您需要特定尺寸,最终应使用int固定宽度类型。请注意,标准整数类型没有定义的位宽。对于32位,请使用stdint.h作为变量。对于常量,请使用宏:uint32_t(不带后缀!)。

答案 3 :(得分:1)

我的想法:第一次打电话给myprint()&#39;是一个表达式,因此必须在运行时计算。所以编译器需要将它(通过生成的指令)解释为带符号的 int 左移,产生负的签名 int ,这是然后签名扩展以填充,然后重新解释为 unsigned long (我认为这可能是编译错误?)

相比之下,第二次打电话给'myprint()&#39;是一个硬编码的整数常量表达式,传递给以 unsigned long 作为参数的例程;我认为编译器是为了从这个上下文中假设常量表达式已经 unsigned long ,因为没有公开冲突的类型信息。

答案 4 :(得分:0)

如果我错了,请纠正我。这就是我所理解的。

在我的机器上,如M.M所说,sizeof(int)= 4.(通过print sizeof(int)确认)

因此,1&lt;&lt; 31变为(带符号)0x80000000,因为1被签名。 但是,0x8000000变为无符号,因为它不适合signed int(因为它被视为正数,而int的最大正数可以是0x7fffffff)。

因此,当signed int转换为long时,将发生符号扩展(使用符号位进行扩展)。转换unsigned int时,使用0进行扩展。

这就是为什么在myprint(1&lt;&lt; 31)的情况下会有额外的1,并且在任何一种情况都不是这样的

1)myprint(1u <&lt; 31)

2)myprint(1&lt;&lt; 31),当int&gt; 32位,因为在这种情况下符号位不是1.