2016-04-08 38 views
6

考虑简单的代码:虽然(i--)s + = a [i];在C和C++中包含未定义的行为?

#include "stdio.h" 

#define N 10U 

int main() { 
    int a[N] = {0}; 
    unsigned int i = N; 
    int s = 0; 

    // Fill a 

    while(i--) 
     s += a[i]; 

    printf("Sum is %d\n", s); 

    return 0; 
} 

是否while循环包含未定义因为整数下溢的行为?编译器是否有权假定while循环条件总是如此,并最终导致无限循环?

如果isigned int?它是否包含与数组访问相关的陷阱?

更新

我跑这一点,类似的代码很多次,它工作得很好。此外,它是向后遍历数组和向量的流行方式。我问这个问题,以确保从标准的角度来看这种方式是可以的。

一眼看来,它显然不是无限的。另一方面,有时编译器可以“假设代码不包含未定义的行为”来“优化”一些条件和代码。它会导致无限循环和其他不必要的后果。见this

+3

那么,这是**不是**无限循环。 –

+2

当你运行它时发生了什么? –

+0

没问题,因为'while(0)'是错误的,之后你不关心'i'的值 – MrTux

回答

6

由于整数下溢,while循环是否包含未定义的行为?

不,在有符号整数的情况下,上溢/下溢只是未定义的行为。

编译器是否有权假定while循环的条件总是如此,并最终导致无限循环?

不,因为表达式最终会变成零。

如果我签署了int,该怎么办?它是否包含与数组访问有关的陷阱?

如果它被签名和溢出/下溢,则会调用未定义的行为。

+0

's /只是未定义的行为/只可能/' –

+0

@LightnessRacesinOrbit随意向C标准委员会提交更正,3.4.3'“示例未定义行为的示例是整数溢出行为。 ' – Lundin

+1

该陈述是正确的。但是无符号值不会溢出,所以与这种情况无关。你的答案中的短语在技术上也是正确的,但我的观点是,我认为它提示了错误的事情 - 对于_unsigned_整数,溢出/下溢不是_defined_行为,因为它是不可能的! :)因此,我的建议是自然界的编辑。 –

9

此代码不会调用未定义的行为。一旦i变为0,循环将被终止。对于unsigned int,不存在整数溢出/下溢。效果将与i相同signed除了在这种情况下不会包装。

+0

为什么要投票? – haccks

+2

这是C++的标签,对所有答案都有一个1/5的downvote比例。 (有一个+1来抵消。) –

3

i将绕回~00XFFFFFFFF为32位),但循环将终止,因此没有UB。

+0

“但循环将终止,因此没有UB。” - nononono,这是相反的方式。 **因为**没有UB,所以**编译器必须发出使循环终止的代码。反过来,定义行为的原因是递减的对象('i')是'unsigned'。 –

4

由于以下原因,循环不会产生未定义的行为。

i初始化为10,并在循环中递减。当i的值为零时,递减该值将产生一个等于UINT_MAX的值 - unsigned可表示的最大值,但循环将终止。对于在N-1(即9)和0之间的i的值,将仅访问(在循环内)a[i]。这些都是数组a中的所有有效指数。

s以及a的所有元素都被初始化为零。因此,所有添加将0添加到0。这永远不会溢出或下溢int,所以永远不会导致未定义的行为。

如果i更改为signed int,则递减永不下溢,并且当循环终止时i将具有负值。循环后唯一的净变化是i的值。

0

由于整数下溢,while循环是否包含未定义的行为?

首先这是一个Boolean Conversion

积分,浮点,无作用域枚举,指针,指针到成员类型的prvalue可以转换为bool类型的prvalue。

值为零(用于积分,浮点和非范围枚举)以及空指针和空指针至成员值变为false。所有其他值变为true

所以不会有整数下溢如果i正确初始化,将达到0U和将被强制转换为false值。

那么编译器有效地在这里做的是:while(static_cast<bool>(i--))

难道编译器都正确的假设,while循环总是true因为这一点,带着无尽的循环结束了?

这不是无限循环的关键原因是postfix decrement operator返回减量变量的值。它的定义为T operator--(T&, int)正如之前讨论的那样,编译器将评估该值是否为0U,作为转换为bool的一部分。

什么编译器有效地在这里做的是:while(0 != operator--(i, 1))

在你更新你举这个代码作为你的问题的一个动机:

void fn(void) 
{ 
    /* write something after this comment so that the program output is 10 */ 
    int a[1] = {0}; 
    int j = 0; 
    while(a[j] != 5) ++j; /* Search stack until you find 5 */ 
    a[j] = 10;    /* Overwrite it with 10 */ 
    /* write something before this comment */ 
} 

经检查,这整个程序已经未定义的行为有只有a的1个元素,并且它已初始化为0.因此,对于除0以外的任何索引,a[j]都在关注数组的末尾。这将持续到找到5,或者直到操作系统错误,因为程序已从受保护的内存中读取。这与您的循环条件不同,后缀递减运算符返回值为0时将退出,因此不能假定这始终为true,或者循环将无限持续。

如果isigned int

上述两种转换均内置于C++。它们都是为所有整数类型定义的,有符号和无符号。所以最终这扩展到:

while(static_cast<bool>(operator--(i, 1))) 

它是否包含与数组访问有关的陷阱?

同样,这调用内置的操作中,subscript operatorT& operator[](T*, std::ptrdiff_t)

所以a[i]是调用operator(a, static_cast<ptrdiff_t>(i))

相当于所以显而易见的后续问题是什么是ptrdiff_t?它是一个实现定义的整数,但是因为标准的每个实现都负责定义这种类型的转换,所以i将正确转换。

+0

运算符返回引用与什么有关?那么'operator ++'如何进入呢?你的困惑。 –

+0

@LightnessRacesinOrbit谢谢。在'operator ++'键入错误的东西。但是,你的意思是什么?“操作符 - 返回一个引用与什么有什么关系?”如果这不能返回某些“时间”条件没有任何评估的东西, –

+1

它可以通过价值回报,而事实上它的确如此。后缀减少_has to_(对于内置的算术类型,前缀减量无论如何,根据您链接到的引用)。此外,很显然,'我 - '评估的东西,否则代码将无法编译。我认为整个部分对于这个问题来说是一条红色的鲱鱼。 –

1

您的代码表现良好,并且包含N个≥的任何值没有未定义的行为0

让我们集中讨论的while循环中,并跟踪通过的执行例子与N = 2

// Initialization 
#define N 2U 
unsigned int i = N; // i = 2 

// First iteration 
while(i--) // condition = i = 2 = true; i post-decremented to 1 
    s += a[i]; // i = 1 is in bounds 

// Second iteration 
while(i--) // condition = i = 1 = true; i post-decremented to 0 
    s += a[i]; // i = 0 is in bounds 

// Third iteration 
while(i--) // condition = i = 0 = false; i post-decremented to 0xFFFFFFFF 
// Loop terminated 

我们可以从这个跟踪看出a[i]始终处于界限,i从0递减时,这是非常明确的经历的签名环绕。

要回答你的第二个问题,如果我们改变了i类型signed int,改变在示例跟踪的唯一行为是循环终止在同一个地方,但i被减少到-1,这也是非常清楚-defined。

因此总而言之,假设N≥0,无论iunsigned int还是signed int,您的代码都表现良好。 (如果N是负的,并且isigned int,则循环将保持递减,直到在INT_MIN处发生未定义的行为。)

+0

非常明确的解释,谢谢! –

相关问题