使用无符号“负”数字递减指针

时间:2017-07-19 12:58:01

标签: c++ c gcc

调用look_back_1()或look_back_2()时,下面的示例应该会崩溃。 原因:当否定无符号变量时,结果应保持无符号。

#include <stdio.h>


int look_back_1(int *arr, unsigned int nmElems, unsigned long dist)
{
    int *elem = arr + nmElems;
    elem += -dist;
    return (*elem);
}


int look_back_2(int *arr, unsigned int nmElems, unsigned int dist)
{
    int *elem = arr + nmElems;
    elem += -dist;
    return (*elem);
}


int main(int argc, char **argv)
{
    int arr[100] = { 0, };
    printf("1. %d\n", look_back_1(arr, 100, 1)); //       <NEEDS TO CRASH, BUT WORKS????>>
    printf("2. %d\n", look_back_2(arr, 100, 1)); //       <<CRASH!!!!!>>
}

当进行数组越界访问时,GCC 4.5在每个函数调用中崩溃。 编译器为这两种情况发出NEG操作码。

GCC 6.1或Clang只会在调用int版本时崩溃。 但是当它们为unsigned long版本发出SUB操作码时,它们都避免了崩溃。

他们是否允许这样做?

2 个答案:

答案 0 :(得分:4)

[编辑]这是问题的上一版本的答案,它显示了使用参数dist==1调用这些函数时的问题

-(unsigned long)1定义明确并且包装好。它只是ULONG_MAX。出于同样的原因,-(unsigned int)UINT_MAX

数组边界外的指针运算会导致未定义的行为,因此GCC忽略这种可能性是完全合理的。例如,它们可以将x64上的指针视为带有环绕的64位整数。将64位ULONG_MAX添加到带有环绕的64位指针只会将指针减少-1,这就是环绕的工作方式。在UINT_MAX附近添加32位int[100]点。

因此,您看到的行为是未定义行为的完全有效后果。然而,它完全不可靠。优化器可能知道您不能添加超过数组中允许的最大元素数(在64位平台上4字节整数将为2 ^ 62),并从那里做出假设。

答案 1 :(得分:0)

看看你的“godb​​olt”拆卸,差异很容易。您正在编译一个原生64位的平台,unsigned int 32位和unsigned long 64位。也就是说,数学本身是模2 ^ 64,它与unsigned long的行为完全匹配。但是对于unsigned int,需要一条额外的指令。这是从32位寄存器到其自身(!)的细微MOV指令。这个指示的原因是什么?它清除64位结果的高32位,这就是“模2 ^ 32”行为所需的。

这很有效,也很聪明。对于展示未定义行为的代码,它可能会给出意想不到的结果,但无论如何你都不应该对这些案例抱有期望。