循环条件下处理size_t下溢的惯用法

问题描述 投票:3回答:5

在C和C ++中,size_t是用于表示大小的无符号类型。它表示意图,并在某种程度上简化了范围断言(有符号整数的len < upper_boundlen >= 0 && len < upper_bound)。

(在下面的所有示例中,len表示数组a的长度。

for循环的惯用法是:for (i = 0; i < len; i++)。向后for循环的惯用法是for (i = len-1; i >= 0; i--)。但是拥有无符号的循环索引会引入一些细微的错误,而且我经常把一些混乱的情况弄糟。

首先,向后for循环。 len = 0,此代码下溢。

for (size_t i = len-1; i >= 0; i--) { // Bad: Underflows for len=0
    use(a[i]);
}

-->个“运算符”技巧,如果您不习惯的话,这看起来很奇怪。

for (size_t i = len; i--> 0;) {
    use(a[i]);
}

您可以对循环索引变量使用带符号的类型,但是如果len > INT_MAX则溢出。许多人和组织认为这种风险非常小,以至于只坚持[C0​​]。

int

因此,我已经选择了此构造,因为它最接近规范的for循环形式,并且具有最简单的表达式。

for (int i = len-1; i >= 0; i--) {  // BAD: overflows for len < INT_MAX
    use(a[i]);
}

我的问题从for (size_t i = len; i > 0; i--) { size_t pos = i-1; use(a[pos]); } 迭代到0

即,在[0,len-1)范围内循环。当len-1时,此循环下溢。

len=0

对于向后迭代的情况,可以使用带符号整数,但这可能会导致溢出。

for (size_t i = 0; i < len-1; i++) {   // BAD: Underflows for len=0.
    use(a[i]);
}

我倾向于在循环条件中添加另一个表达式,检查for (int i = 0; i < len-1; i++) { // BAD: Will overflow if len > INT_MAX use(a[i]); } ,但感觉很笨拙。

len > 0

我可以在循环之前添加一个if语句,但这也很笨拙。

是否有一种比较简单的写for循环的方法,其无符号索引变量从0循环到len-1?

在C和C ++中,size_t是用于表示大小的无符号类型。它表示意图,并在一定程度上简化了范围断言(len = 0 && len <...>

这里有两种情况。

for (size_t i = 0; len > 0 && i < len-1; i++) { use(a[i]); } 向前迭代到0(包括)

len - 2

for (size_t i = 0; i + 1 < len; ++i) { size_t index = i; // use index here } 向后迭代到len - 2在内>

0

怎么样

for (size_t i = len; i > 1; --i) {
    size_t index = i - 2;
    // use index here
}

在所有这些情况下,您都有一个共同的主题:您的索引具有起始值。它会递增或递减,直到达到终止循环的结束值。

这里有用的一件事是使这两个值明确。在最简单的情况下,向前迭代整个范围,最终值就是for (size_t i = 0; i+1 < len; i++) { use(a[i]); }

len

这遵循了给予使用迭代器的人们的一般建议。特别要注意比较,它在这里有效,并且某些迭代器实际上是必需的。

现在,向后迭代:

for (size_t i=0, end=len; i!=end; ++i) {
    ///...
}

最后,向后迭代排除范围的最后for (size_t i=len-1, end=-1; i!=end; --i) { ///... } 个元素的子集:

n

实际上,您正在与之争的是您试图将过多的内容放入循环逻辑中。只需明确地说,这需要超过if (len > n) { for (size_t i=len-n-1, end=-1; i!=end; --i) { ///... } } 个元素才能完成所有操作。是的,您可以将n放入循环条件中,但这不会给您清楚而简单的代码,这是任何人都可以理解的意图。

我倾向于在循环条件中添加另一个表达式,检查len> 0,但感觉很笨拙。

您的代码应遵循逻辑。这一点都不笨拙。而且它对于人类来说更具可读性。

由于len > n和我通常使用if语句,所以循环没有意义。它使代码易于理解和维护。

len == 0

或者您也可以在循环中添加支票。顺便说一句,您只检查它是否不为零。

if(len)
{
    for (size_t i = 0; i < len-1; i++) { /*...*/ }       
}

这个问题有很多可能的答案。最好的是基于意见的。我会提供一些选择。

为了使用无符号索引以相反顺序迭代数组的所有元素,一个选项是

 for (size_t i = 0; len && i < len-1; i++) { /*...*/ }       

或(更简单地说)

for (size_t index = 0; index < len; ++index)
{
     size_t i = len - 1 - index;
     use(a[i]);
}

[在两种情况下,如果for (size_t i = 0; i < len; ++i) { use(a[len - 1 - i]); } 为零,则不执行循环体。尽管循环是递增而不是递减,但是两个访问元素的顺序相反。如果您经常编写这样的循环,则不难编写如下形式的内联函数

len

和做

size_t index_in_reverse(size_t index, size_t len)
{
     return len - 1 - index;
}

为了以向前的顺序遍历循环的所有元素(最后一个除外),我将其明确化,而不是尝试在循环条件下进行。

for (size_t i = 0; i < len; ++i)
{
     use(index_in_reverse(i, len));
}

我引入变量if (len > 0) { size_t shortened_len = len - 1; for (size_t i = 0; i < shortened_len; ++i) use(a[i]); } 的原因是使代码能够自我记录有关未遍历整个数组的事实。我见过太多情况,随后的开发人员“纠正”了shortened_len形式的条件以删除i < len - 1,因为他们认为这是一个错字。

这可能会使OP感到“笨拙”,但我建议

- 1

对于人类而言,更难理解(将多个测试置于循环条件下,迫使维护代码的人员实际确定这两种条件的作用以及它们如何相互作用)。如果在“简洁”的代码或“简洁程度不高但不熟悉的人花更少的脑力来理解的代码”之间进行选择,我将始终选择后者。请记住,六个月后维护代码的用户可能是您本人,没有什么比“白痴是怎么写的?噢,是我!”的思想过程更为令人毛骨悚然。

c++ c for-loop unsigned size-t
5个回答
1
投票

这里有两种情况。


0
投票

怎么样


0
投票

在所有这些情况下,您都有一个共同的主题:您的索引具有起始值。它会递增或递减,直到达到终止循环的结束值。


0
投票

我倾向于在循环条件中添加另一个表达式,检查len> 0,但感觉很笨拙。


0
投票

这个问题有很多可能的答案。最好的是基于意见的。我会提供一些选择。

© www.soinside.com 2019 - 2024. All rights reserved.