您如何发现或避免犯错?[关闭]

How do you spot or avoid committing off-by-one errors? [closed]

提问人:Ray Hamel 提问时间:4/27/2021 更新时间:4/27/2021 访问量:443

问:


想改进这个问题吗?更新问题,使其仅通过编辑这篇文章来关注一个问题。

2年前关闭。

去年,社群审查了是否要重新讨论这个问题,并关闭了这个问题:

原始关闭原因未解决

当我编写代码时,我创建的更常见的错误类是差一错误 (OBO)。在“真实”代码中,我最常在用 C 或 C++ 进行复杂的指针/迭代器算术时遇到这个问题,但我有时在工作面试和家庭作业(例如实现合并排序)问题中也遇到问题,我使用其他编程语言。

我的典型调试策略包括随机交换 和 、 和 以及 附加 或 到变量,直到代码看起来正常工作。我很想知道有什么更好的策略或工具(例如静态分析、调试器/IDE 功能等)来发现 OBO。<<=++varvar+++ 1- 1

我对诸如“避免 C 样式循环”、“避免指针算术”、“函数式代码”或“使用不同的编程语言”之类的 pat 答案并不特别感兴趣。是的,这些事情可以缓解问题,但并不总是可以做到,而且它们并不能完全消除 OBO 危险。

例如,我相信以下代码,C(或者它也是有效的 C++)实现,没有 OBO。但正如你所看到的,OBO可能会出现数十个潜在的问题领域。有没有办法轻松确认它实际上没有 OBO?strcpy(3)

#include <stdbool.h>
#include <stdint.h>
#include <string.h>

#if !defined(__GNUC__) && !defined(__attribute__)
#define __attribute__(X)
#endif

#ifdef __cplusplus
extern "C" {
#if defined(__GNUC__) || defined(_MSC_VER) || defined(__restrict)
#define restrict __restrict
#elif !defined(restrict)
#define restrict
#endif
#endif

static inline bool aligned8(const void *ptr) {return !((uintptr_t)ptr & 7);}
static inline bool aligned4(const void *ptr) {return !((uintptr_t)ptr & 3);}
static inline bool aligned2(const void *ptr) {return !((uintptr_t)ptr & 1);}

/* Positive return value is the greatest common alignment (≤8) of the pointers.
 * Negative return value is the byte offset that when added to both pointers
 * yields a pair of pointers with alignment ≥8.
 */
static inline int common_alignment(const void *p1, const void *p2) {
    if (aligned8(p1)) {
        if (aligned8(p2))
            return 8;
        else if (aligned4(p2))
            return 4;
        else if (aligned2(p2))
            return 2;
    } else if (aligned4(p1)) {
        if (aligned8(p2))
            return 4;
        else if (aligned4(p2))
            return -4;
        else if (aligned2(p2))
            return 2;
    } else if (aligned2(p1)) {
        if (aligned4(p2))
            return 2;
        else if (aligned2(p2))
            return -6;
    } else if (!aligned2(p2)) {
        return -7;
    }
    return 1;
}

/* strcpy implementation
 */
__attribute__((nonnull))
size_t string_copy(char *restrict dst, const char *restrict src) {
    size_t i = 0;
    int align = common_alignment(dst, src);

    if (align < 0) {
        for (; i < (size_t)-align; ++i)
            if (!(*dst++ = *src++))
                return i;
        align = 8;
    }

    const size_t mask = (size_t)align - 1;

#define ALIGNED_STRING_COPY_IMPL_(BITS) do {\
    uint##BITS##_t *restrict dst##BITS = (uint##BITS##_t*)dst;\
    while (*src++)\
        if (!(++i & mask))\
            *dst##BITS++ = *(const uint##BITS##_t *restrict)(src - align);\
    dst = (char*)dst##BITS;\
} while (0)

    if (align & 8) {
        ALIGNED_STRING_COPY_IMPL_(64);
    } else if (align & 4) {
        ALIGNED_STRING_COPY_IMPL_(32);
    } else if (align & 2) {
        ALIGNED_STRING_COPY_IMPL_(16);
    } else { // byte-aligned
        while ((*dst++ = *src++))
            ++i;
        return i;
    }

    const size_t offset = (i & mask) + 1;

    memcpy(dst, src - offset, offset);

    return i;
}

#ifdef __cplusplus
}
#endif
C++ C 与语言无关 的指针算术 off-by-one

评论

0赞 Thomas Matthews 4/27/2021
您可能需要运行静态分析工具。
1赞 Eugene Sh. 4/27/2021
我不知道它有任何“正式”或自动化的方法(除非它导致一些可以通过分析工具发现的非法访问)。只是每次遇到潜在的OBO时,你都需要仔细考虑,也许在纸上画一些图表。
0赞 Thomas Matthews 4/27/2021
检查代码时,请使用清单。添加“减少一个”作为清单中的项目之一。
2赞 Eugene Sh. 4/27/2021
我典型的调试策略包括随机交换 < 和 <=、++var 和 var++,并将 + 1 或 - 1 附加到变量中,直到代码看起来正常工作 - 这绝对不是正确的方法。当你发现一个偏离一个错误时,你需要找到它的根源,并在那里修复它,否则你会在一个地方修复它,它会在另一个地方重新出现。
2赞 Pete Becker 4/27/2021
编写并运行符合边界条件的测试用例。

答: 暂无答案