简体   繁体   English

检测并防止 C 中的 unsigned long 溢出

[英]Detect and prevent overflow for unsigned long in C

I have some lines in my code to check whether the resulting value overflows (by comparing it to it's previous iteration), and therefore if the input value is too large.我的代码中有一些行来检查结果值是否溢出(通过将其与之前的迭代进行比较),因此输入值是否太大。 This works for some values, but not for values whose increment is so large that it not only overflows, but overflows so much that the resulting value is larger than the previous iteration.这适用于某些值,但不适用于增量如此之大以至于它不仅溢出,而且溢出太多以至于结果值大于前一次迭代的值。 For example, it triggers for 18446744073709551616 ( MAX_ULONG + 1 ), but not for 184467440737095516150 ( MAX_ULONG * 10 ).例如,它会触发18446744073709551616 ( MAX_ULONG + 1 ),但不会184467440737095516150 ( MAX_ULONG * 10 )。 How can I address this issue?我该如何解决这个问题? The code is as follows:代码如下:

unsigned long result = 0;
unsigned long overflowCheck = 0;
int power = 0;

for (int i = (strlen(input) - 1); i >= 0; i--) {
    if ((input[i] > ('0' - 1)) && (input[i] < ('9' + 1))) {
        result += (input[i] - '0') * (unsigned long)pow(iBase, power++);
    } else {
        printf("Invalid input string.");
        valid = 0;
        return -1;
    }
    if (result < overflowCheck) {
        printf("Input value too large.");
        valid = 0;
        return -1;
    }
    overflowCheck = result;
}
return result;

There are multiple problems in your code:您的代码中有多个问题:

  • you should not use pow to perform integer arithmetics: type double may have less value bits than type unsigned long (for example on 64-bit linux, double has 53 value bits and unsigned long has 64).您不应该使用pow来执行 integer 算术: double类型的值位可能少于unsigned long类型(例如在 64 位 linux 上, double有 53 个值位, unsigned long有 64 个)。 It is simpler to multiply the current value by iBase and add the digit value for each new digit parsed.将当前值乘以iBase并为解析的每个新数字添加数字值更简单。
  • it is easier to detect potential overflow before multiplying or adding the values.在相乘或相加之前更容易检测到潜在的溢出。

Here is a modified version:这是修改后的版本:

#include <errno.h>
#include <limits.h>

unsigned long atoul(const char *input, unsigned int iBase) {
    if (iBase < 2 || iBase > 36) {
        errno = EINVAL;
        return 0;
    }
    unsigned long result = 0;
    unsigned long maxval = ULONG_MAX / iBase;
    int maxdigit = ULONG_MAX % iBase;

    for (;;) {
        int c = *input++;
        int digit;
        if (c >= '0' && c <= '9') {
            digit = c - '0';
        } else
        if (c >= 'A' && c <= 'Z') {
            digit = c - 'A' + 10;
        } else
        if (c >= 'a' && c <= 'z') {
            digit = c - 'a' + 10;
        } else {
            break;
        }
        if (digit >= iBase)
            break;
        if (result > maxval || (result == maxval && digit > maxdigit) {
            /* overflow detected */
            errno = ERANGE;
            return ULONG_MAX;
        }
        result = result * iBase + digit;
    }
    return result;
}

Suppose you want to check if x + y overflows where x and y are both unsigned long .假设您要检查x + y是否溢出xy都是unsigned long The naive approach would be to do this:天真的方法是这样做:

if (ULONG_MAX < x + y)

But this will always be false because of overflow.但是由于溢出,这总是错误的。 Instead, you would do this:相反,你会这样做:

if (ULONG_MAX - x < y)

This check is algebraically the same as the first attempt but avoids issues of overflow.此检查在代数上与第一次尝试相同,但避免了溢出问题。 You can do a similar check in your case:您可以在您的情况下进行类似的检查:

if ((input[i] > ('0' - 1)) && (input[i] < ('9' + 1))) {
    int digit = input[i] - '0';
    if (ULONG_MAX / 10 < result) {
        printf("overflow");
        return -1;
    }
    result *= 10;
    if (ULONG_MAX - digit < result) {
        printf("overflow");
        return -1;
    }
    result += digit;
} else {
    printf("Invalid input string.");
    valid = 0;
    return -1;
}

result < 0 will always return false since result is unsigned (and can never be less than 0. One way to check for overflow is to make sure pow() (as a double ) is within the bounds for long . However, the real solution here is to not use pow() and keep everything as integers. If you work starting with the most significant digit, you can multiply result by the base (16 in this case) and add the new digit each time. This works because 1234 = base*(base*(base*(0 + 1) + 2) + 3) + 4 result < 0将始终返回 false,因为result是无符号的(并且永远不能小于 0。检查溢出的一种方法是确保pow() (作为double )在long范围内。但是,真正的解决方案这里是不要使用pow()并将所有内容都保留为整数。如果您从最高有效数字开始工作,您可以将结果乘以基数(在这种情况下为 16)并每次添加新数字。这是因为1234 = base*(base*(base*(0 + 1) + 2) + 3) + 4

Some (incomplete) code would be:一些(不完整的)代码将是:

int input_len = strlen(input);
for (int i = 0; i < input_len; i++) {
    // After finding out which digit group input[i] is in:
    result = result * iBase + (input[i] - '0');
}

Since result will only change by a factor of 16 at most, you can check for overflow by comparing with the previous result every iteration:由于result最多只会改变 16 倍,因此您可以通过每次迭代与之前的结果进行比较来检查溢出:

unsigned long previous = result;
// Add in the next digit
if (result < previous) {
    // Overflow
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM