【问题标题】:How is it possible for 0 <= -1 to return true? [duplicate]0 <= -1 怎么可能返回 true? [复制]
【发布时间】:2018-09-13 08:06:11
【问题描述】:

我编写了一个小程序作为示例来复制我遇到的问题。程序以这种方式获取和操作两行代码:

  1. 它用新行分隔行。
  2. 每一行由若干个字母和一个空格(在本例中为' ')和一个数字组成。
  3. 它将字母复制到另一个字符串,然后添加'\0'
  4. 它将数字复制到另一个字符。
  5. 它打印字符串,其中是复制的字母和 int,其中是转换后的复制数字。

这是最小的程序:

#include <stdio.h>
#include <string.h>

void read(char *text)
{
    // create and initialize the line holder
    int k = 0;
    char line[10];
    line[0] = '\0';
    for (int i = 0;text[i] != '\0';i++)
    {
        if (text[i] == '\n')
        {
            // k now points to the character right after the last assigned one, so put 0 in that place
            line[k] = '\0';

            // initialize data objects that will hold text and number
            char letters[5];
            letters[0] = '\0';
            char val;

            // step through the line, and stop if you 1. reached a blank or 2. reached the end of a line
            int j = 0;
            while (line[j] != ' ' && line[j] != '\t' && j <= (strlen(line) - 1))
            {
                printf("%d <= %ld = %d\n", j, strlen(line) - 1, j <= (strlen(line) - 1));
                if (j == (strlen(line) - 1)) // reached the last character before reaching blank
                    return;
                letters[j] = line[j];
                j++;
            }

            letters[j] = '\0'; // where should be blank place 0

            if (j + 1 == (strlen(line) - 1)) // if the next character is the last character, meaning that the character before the last one is blank
                val = line[j + 1];
            else // there is space in that is not one before the last character
                return; // this is where read("\n") should stop, but withou entering the while loop!

            printf("Word: %s\tVal: %d\n", letters, val - '0');

            // empty the line holder
            line[0] = '\0';
            k = 0;
        }
        else
        {
            // place the ith text character into the kth line character and print them
            line[k] = text[i];
            printf("line[k] = %c\ttext[i] = %c\n", line[k], text[i]);

            // increment k for the next turn
            k++;
        }
    }
}

int main()
{
    char *text = "ABCD 0\nEFGH 1\nIJKL 2\nMNOP 3\nQRST 4\nUVWX 5\nYZ 5\n";
    read(text);
    printf("---------------------------------\n");
    read("\n");
    return 0;
}

如果检测到错误,该程序也应该终止而不执行其工作。这些点由return 关键字和read(char *text) 函数中的cmets 指示。它们只有两个,所以我也将在这里描述它们:

第 28 行:如果检测到当前字符是最后一个字符,程序将停止扫描此行。由于最后一个字符应该始终以空格开头,这意味着我们在没有退出 while 循环的情况下到达了行尾(如果我们到达 ' ''\t' 就会发生这种情况)。

第 38 行:如果我们成功退出了 while 循环,则字符 jline 的偏移应该是空白。那是因为我们在发现空白时退出了 while 循环(这也是因为我们以 line[j] = '\0' 结束 line)。这也意味着j+1 应该是一个数字,它是该行中的最后一个字符。如果不是这样,我们到达了不在数字之前的空白处,所以我们退出了函数。


那么,问题出在哪里?如您所见,我将两个字符串传递给read(char *text) 函数。 read(char *text) 完美地操作和打印第一个字符串。对于第二个,只有"\n",这个功能不能很好地工作。我不明白的部分是我们进入了while循环,尽管条件j &lt;= strlen(line) - 1)text = "\n"时以某种方式返回1。您可以看到,通过运行程序,它会在第 26 行打印该信息。

【问题讨论】:

  • 请注意,strlen 返回一个size_t 值,即无符号。现在想想当strlen 返回0 并从中减去1 时会发生什么......
  • 你不能将你的函数命名为read,因为已经有一个该名称的标准函数,即使你避免包含定义标准函数的头文件,这也会为编译器保留名称。
  • 为什么不改用j &lt; strlen(line)
  • 阅读how to debug small programs。启用所有警告和调试信息,例如gcc -Wall -Wextra -g 使用时GCC
  • @PascalCuoq:你真的确定吗? IIRC read 是 POSIX 函数,而不是 C11 标准函数。但实际上你是完全正确的。给函数命名read是不合理的

标签: c


【解决方案1】:

strlen(line) - 1中,strlen(line)的类型是size_t,一个无符号整数类型。在您的编译平台上定义size_tC's promotion rules 使减法成为size_t(无符号)减法,得到size_t(无符号)结果。结果为(size_t)-1,通常为0xffffffff0xffffffffffffffff

虽然没有提供上述解释,但this online C interpreter 通过指出您在printf 中为格式%ld 传递了错误的类型来暗示问题。在选定的编译平台上,上述情况适用,printf 参数strlen(line) - 1 的类型为size_t,应打印为%zu

这个无符号算术导致你的程序useline[j],而这个内存位置没有初始化。如果您将所有出现的strlen(line) - 1 更改为(int)strlen(line) - 1,以强制执行int 减法计算签名结果,那么the program does not have undefined behavior

如 cmets 中所述,将 strlen(line) 更改为 (int)strlen(line) 只是一种快速而简单的解决方法,如果 intsize_t 更窄,则会限制程序可以应用的输入范围。正确的解决方法是检查每个涉及strlen 结果的更大表达式并重写它,以便它使用size_t 算术执行程序员的意图。例如,条件j == (strlen(line) - 1) 可以写成(size_t)j + 1 == strlen(line)。这反过来表明许多变量,包括j,应该直接声明为size_t,而不是int

【讨论】:

  • line[j] 未初始化是什么意思?请进一步解释。
  • 哦,谢谢。我将所有strlen(line) 转换为(signed int)strlen(line)。现在一切正常。
  • @Hanlon 我没有立即意识到这一点,但是 line[j] 被使用初始化是另一个奇怪的结果是减法 strlen(line)-1 没有计算出你期望的结果。此后,我更新了我的答案,提出了与您找到的相同的修复方法。
  • 我真的不建议转换为 int - 如果值被截断,您将不会收到任何警告(请记住,INT_MAX 可能只是 32767,具体取决于代码的编译位置)。最好使代码无溢出(可能通过在比较的另一边加 1,将 &lt;= 更改为 &lt;,或者通过在算术之前测试值)。
  • @TobySpeight 我同意您评论的原则,但是当我将其纳入我的回答时,我得出的结论是,如果您不同时更改为 size_t 类型在当前声明为 int 的许多变量中,您并没有使程序变得更加健壮(特别是如果 INT_MAX 只有 32767)。
【解决方案2】:

这类问题(以及许多其他问题)的解决方案是打开编译器警告。

$ clang -Wall -Wextra -std=c11 -pedantic-errors k.c
k.c:24:59: warning: comparison of integers of different signs: 'int' and 'unsigned long' [-Wsign-compare]
            while (line[j] != ' ' && line[j] != '\t' && j <= (strlen(line) - 1))
                                                        ~ ^   ~~~~~~~~~~~~~~~~
k.c:26:67: warning: comparison of integers of different signs: 'int' and 'unsigned long' [-Wsign-compare]
                printf("%d <= %ld = %d\n", j, strlen(line) - 1, j <= (strlen(line) - 1));
                                                                ~ ^   ~~~~~~~~~~~~~~~~
k.c:27:23: warning: comparison of integers of different signs: 'int' and 'unsigned long' [-Wsign-compare]
                if (j == (strlen(line) - 1)) // reached the last character before reaching blank
                    ~ ^   ~~~~~~~~~~~~~~~~
k.c:35:23: warning: comparison of integers of different signs: 'int' and 'unsigned long' [-Wsign-compare]
            if (j + 1 == (strlen(line) - 1)) // if the next character is the last character, meaning that t...
                ~~~~~ ^   ~~~~~~~~~~~~~~~~
4 warnings generated.

比较有符号和无符号确实是这里的问题。

为了执行比较,操作数被隐式转换。 j == (strlen(line) 等价于(size_t)j == (strlen(line),只是后者不会产生警告。

【讨论】:

  • 谢谢!我不知道-Wextra 标志。
猜你喜欢
  • 1970-01-01
  • 2015-01-16
  • 1970-01-01
  • 2019-01-09
  • 2015-04-30
  • 2014-07-03
  • 2012-09-02
  • 1970-01-01
  • 2021-05-16
相关资源
最近更新 更多