C安全地取整数的绝对值

hyd*_*yde 19 c absolute-value undefined-behavior

考虑以下程序(C99):

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>

int main(void)
{
    printf("Enter int in range %jd .. %jd:\n > ", INTMAX_MIN, INTMAX_MAX);
    intmax_t i;
    if (scanf("%jd", &i) == 1)
        printf("Result: |%jd| = %jd\n", i, imaxabs(i));
}
Run Code Online (Sandbox Code Playgroud)

现在据我了解,这包含易于触发的未定义行为,如下所示:

Enter int in range -9223372036854775808 .. 9223372036854775807:
 > -9223372036854775808
Result: |-9223372036854775808| = -9223372036854775808
Run Code Online (Sandbox Code Playgroud)

问题:

  1. 这真的是不确定的行为,如"代码可以触发任何代码路径,其中的任何代码,中风编译器的花哨",当用户输入的差多少,?还是其他一些没有完全定义的味道?

  2. 一个迂腐的程序员如何防止这种情况,而不做任何标准无法保证的假设?

(有一些相关的问题,但我没有找到一个回答上面的问题2,所以如果你建议重复,请确保它回答.)

250*_*501 10

如果imaxabs无法表示结果,如果使用二进制补码可能会发生,则行为未定义.

7.8.2.1 imaxabs功能

  1. imaxabs函数计算整数j的绝对值.如果无法表示结果,则行为未定义.221)

221)最负数的绝对值不能用二进制补码表示.

不做任何假设且始终定义的检查是:

intmax_t i = ... ;
if( i < -INTMAX_MAX )
{
    //handle error
}
Run Code Online (Sandbox Code Playgroud)

(如果使用一个补码或符号幅度表示,则不能使用此if语句,因此编译器可能会给出无法访问的代码警告.代码本身仍然是定义且有效的.)

  • @hyde除了另一个答案不符合标准,而这个是. (2认同)

plu*_*ash 7

一个迂腐的程序员如何防止这种情况,而不做任何标准无法保证的假设?

一种方法是使用无符号整数.无符号整数的溢出行为是明确定义的,就像从有符号整数转换为无符号整数时的行为一样.

所以我认为以下内容应该是安全的(事实证明它在一些非常模糊的系统上可怕地被打破,稍后在帖子中看到改进的版本)

uintmax_t j = i;
if (j > (uintmax_t)INTMAX_MAX) {
  j = -j;
}
printf("Result: |%jd| = %ju\n", i, j);
Run Code Online (Sandbox Code Playgroud)

那么这是如何工作的呢?

uintmax_t j = i;
Run Code Online (Sandbox Code Playgroud)

这会将有符号整数转换为无符号整数.如果它是正值,则值保持不变,如果它为负,则值增加2 n(其中n是位数).这会将其转换为大数(大于INTMAX_MAX)

if (j > (uintmax_t)INTMAX_MAX) {
Run Code Online (Sandbox Code Playgroud)

如果原始数字为正(因此小于或等于INTMAX_MAX),则不执行任何操作.如果原始数字为负数,则运行if块的内部.

  j = -j;
Run Code Online (Sandbox Code Playgroud)

这个数字被否定了.否定的结果显然是负的,因此不能表示为无符号整数.所以它增加了2 n.

所以在代数上我看起来像负面的结果

j = - (i + 2 n)+ 2 n = -i


聪明,但这个解决方案做出了假设.如果C Standard允许INTMAX_MAX == UINTMAX_MAX,则会失败.

嗯,让我们看看这个(我读https://busybox.net/~landley/c99-draft.html这是apprarently之前标准化最后C99草案,如果有的话在最后的标准改变了请告诉我.

当typedef名称仅在初始u的存在或不存在时有所不同时,它们应表示6.2.5中描述的相应的有符号和无符号类型; 如果没有提供相应的类型,则实现不应提供类型.

在6.2.5我看到了

对于每个有符号整数类型,存在相应的(但不同的)无符号整数类型(使用关键字unsigned指定),它使用相同数量的存储(包括符号信息)并具有相同的对齐要求.

在6.2.6.2我看到了

#1

对于unsigned char以外的无符号整数类型,对象表示的位应分为两组:值位和填充位(不需要后者中的任何一个).如果有N个值位,则每个位应表示1和2N-1之间的2的不同幂,因此该类型的对象应能够使用纯二进制表示来表示0到2N-1>的值; 这应该被称为价值表示.任何填充位的值都未指定.39)

#2

对于有符号整数类型,对象表示的位应分为三组:值位,填充位和符号位.不需要任何填充位; 应该只有一个符号位.作为值位的每个位应具有与相应无符号类型的对象表示中的相同位相同的值(如果在有符号类型中有M个值位且在无符号类型中有N,则M <= N).如果符号位为零,则不应影响结果值.

所以是的,似乎你是对的,而有符号和无符号类型必须是相同的大小,它似乎对无符号类型有效,比有符号类型多一个填充位.


好吧,基于上面的分析揭示了我的第一次尝试中的一个缺陷,我写了一个更偏执的变种.这与我的第一个版本有两处不同.

我使用i <0而不是j>(uintmax_t)INTMAX_MAX来检查负数.这意味着即使INTMAX_MAX == UINTMAX_MAX,算法也会对格式大于或等于-INTMAX_MAX的数字执行正确的结果.

我为错误情况添加处理,其中INTMAX_MAX == UINTMAX_MAX,INTMAX_MIN == -INTMAX_MAX -1和i == INTMAX_MIN.这将导致我们可以轻松测试的if条件中的j = 0.

从C标准的要求可以看出,INTMAX_MIN不能小于-INTMAX_MAX -1,因为只有一个符号位,并且值位的数量必须与相应的无符号类型相同或更低.没有比特模式可以代表较小的数字.

uintmax_t j = i;
if (i < 0) {
  j = -j;
  if (j == 0) {
    printf("your platform sucks\n");
    exit(1);
  }
}
printf("Result: |%jd| = %ju\n", i, j);
Run Code Online (Sandbox Code Playgroud)

@plugwash我认为2501是正确的.例如,-UINTMAX_MAX值变为1:( - UINTMAX_MAX +(UINTMAX_MAX + 1)),并且不会被if捕获. - 58分钟前的海德

嗯,

假设INTMAX_MAX == UINTMAX_MAX且i = -INTMAX_MAX

uintmax_t j = i;

在此命令之后j = -INTMAX_MAX +(UINTMAX_MAX + 1)= 1

if(i <0){

我小于零所以我们在if中运行命令

j = -j;

在此命令之后j = -1 +(UINTMAX_MAX + 1)= UINTMAX_MAX

这是正确答案,因此无需在错误情况下捕获它.

  • 聪明,但这个解决方案做出了假设.如果"标准"允许"INTMAX_MAX == UINTMAX_MAX",则会失败. (4认同)
  • @hyde段落*C11 6.2.6.2,p2*表示无符号整数中的值位数可能与相应的有符号整数中的值位数相同.(注意:M <= N).在这种情况下,有符号整数的范围实际上更大,因为有符号整数有一个额外的符号位,这使得它具有负范围 (3认同)