【问题标题】:Very complex C infinite loop debugging非常复杂的 C 无限循环调试
【发布时间】:2017-09-12 04:02:09
【问题描述】:

我想为独立数字着色,而不是任何以字母字符开头并以数字结尾的数字。

谁能猜到为什么当我使用这个程序将一些现有的文本文件写入另一个文本文件时,它在几秒钟内就变成了几兆字节?发生了一些无限循环。

char ch;
_Bool finished = true;
void color();
void skip();

int main()
{
    while (finished)
    {
        ch = getchar();
        if (ch == -1) {
            finished = false;
        }
        if ( (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ) {
            skip();
        }
        else if (ch >= '0' && ch <= '9'){
            color();
        }
        else {
            printf("%c", ch);
        }
    }
    return 0;
}

void color() // color characters that are numbers
{
    printf("\e[31m%c\e[0m", ch);
}

void skip() // skip whole words and still print them
{
    putchar(ch);
    while ((ch = getchar()) != ' ') {
        printf("%c", ch);
    }
}

【问题讨论】:

  • char ch; --> int ch; 因为char 可能无法保持EOF 的值(很可能是-1,但您应该针对@987654328 进行测试@ 宏而不是 -1 明确)。请注意,getchar() 返回的是 int,而不是 char。此外,您应该重新考虑全局变量的使用。
  • 有趣。但是,我又如何将我的int ch 表示为字母?
  • 这不是问题。 1) char 是整数类型(可能是 signedunsigned。2)%c 转换说明符在任何情况下都需要 int 参数(感谢在可变参数函数中执行的整数提升,例如 @ 987654339@).
  • 您要求“猜测”,但更好的方法是调试。例如,查看生成文件的内容会有所帮助。或使用调试器,或添加打印语句(尽管它们会进入您生成的文件)。 PS:skip 在几个方面是错误的。 ericlippert.com/2014/03/05/how-to-debug-small-programs
  • @compute 是的!您还写了关于它的作用的评论。至于哪里出错,在任何空格前遇到EOF就永远不会停止。

标签: c loops stdout stdin infinite-loop


【解决方案1】:

无限循环

您报告的无限循环可能是由于在发布的代码中对EOF 的处理不正确造成的。普通的char 可能是signedunsigned,因此可能无法保存EOF 的值,通常是-1

skip() 函数也无法解释可能遇到EOF 的可能性。在这里,如果在空格之前遇到EOF,例如在文件的最后一个单词中,将导致无限循环。

头文件

发布的代码缺少头文件。至少需要#include &lt;stdio.h&gt;#include &lt;stdbool.h&gt;。出于显而易见的原因,需要stdio.h,但也需要stdbool.h,因为这是定义truefalse 宏的地方。 _Bool 类型仍然可以使用,但最好使用stdbool.h 中定义的typedef bool

函数声明

函数原型中的空括号表示未指定数量的参数。 void 必须用于指定要采用 no 参数。这种在函数声明器中使用空括号是该语言的一个过时特性,无论如何都不应该使用。

ANSI 转义码

在 C 中打印颜色的能力取决于平台和终端仿真器。一些教程使用\e 作为转义字符的替代,但是要在C 中生成这个字符,您可能应该使用\x1b(ESC 字符的ASCII 十六进制值;您也可以使用\033,即ESC 的 ASCII 八进制值)。我对标准的解读是 \e 不是有效的字符常量,使用它会产生编译器警告:

警告:非 ISO 标准转义序列,'\e'

但是,这在 Linux 上的 GCC 中似乎对我有用,所以我怀疑这是一个扩展。

逻辑问题

skip() 函数无法检查EOF,但遇到空格时也无法打印。此外,换行符应该能够在数字之前,因为这表示换行符。不正确处理会导致无法突出显示输入第一行之后的行上的初始数字。这些问题可以通过在循环条件中测试EOF\n 来解决,并通过打印导致循环终止的字符如果它是空格或换行符

void skip(void) // skip whole words and still print them
{
    putchar(ch);
    while ((ch = getchar()) != ' ' && ch != '\n' && ch != EOF) {
        printf("%c", ch);
    }
    if (ch == ' ' || ch == '\n') {
        putchar(ch);
    }
}

main() 也有类似的问题。当在主循环中遇到EOF 时,最终的printf() 仍然被执行,在不应该打印的时候打印一个字符。一种解决方案是将EOF 测试之后的语句放在else 块中,尽管有更好的解决方案。

while (finished)
{
    ch = getchar();
    if (ch == EOF) {   // use EOF instead of -1
        finished = false;
    } else {           // need this to avoid printing a character for EOF
        if ( (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ) {
            skip();
        } else if (ch >= '0' && ch <= '9') {
            color();
        }
        else {
            printf("%c", ch);
        }
    }
}

这是目前为止的程序。据我从您对预期行为的描述中可以看出,这可以按预期工作:

#include <stdio.h>
#include <stdbool.h>

int ch;
bool finished = true;

void color(void);
void skip(void);

int main(void)
{
    while (finished)
    {
        ch = getchar();
        if (ch == EOF) {
            finished = false;
        } else {
            if ( (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ) {
                skip();
            } else if (ch >= '0' && ch <= '9') {
                color();
            }
            else {
                printf("%c", ch);
            }
        }
    }
    return 0;
}

void color(void) // color characters that are numbers
{
    printf("\x1b[31m%c\x1b[0m", ch);
}

void skip(void) // skip whole words and still print them
{
    putchar(ch);
    while ((ch = getchar()) != ' ' && ch != '\n' && ch != EOF) {
        printf("%c", ch);
    }
    if (ch == ' ' || ch == '\n') {
        putchar(ch);
    }
}

改进

可以对此代码进行许多改进。

根本不需要bool。当遇到EOF 时,break 语句可以简单地终止主循环。在这种情况下,主循环需要无限期地执行,使用while (1) {},或者更好的是for (;;) {}。更好的是,使用while ((ch = getchar()) != EOF) {} 在循环条件下测试ch

删除全局变量finished后,我们还可以将ch局部化为main()。这可能需要将ch 的值传递给color()skip(),在这种情况下需要更改函数签名。但是请注意,没有理由将字符传递给skip(),因为在调用skip() 之前,可以简单地在main() 中打印该字符。此外,不需要color() 函数,因为这个单行函数可以简单地手动内联。

没有必要在putchar() 的地方使用printf()

最好#define 几个转义码宏。这更容易阅读,也更容易修改。

最后,最好使用ctype.h 中的函数来检查字符是数字、字母字符还是空白字符。与发布代码中的直接比较相比,这更便携,更不容易出错。通过在skip() 函数中使用isspace(),会自动检查\n 字符,避免之前忘记测试行尾的问题。这也处理其他空白字符,例如\t。请注意,ctype.h 中的函数需要可以表示为 unsigned char 值的参数,因此这里需要进行转换。

这是改进后的代码:

#include <stdio.h>
#include <ctype.h>

#define RED    "\x1b[31m"
#define RESET  "\x1b[0m"

void skip(void);

int main(void)
{
    int ch;
    while ((ch = getchar()) != EOF) {
        if (isalpha((unsigned char) ch)) {
            putchar(ch);
            skip();
        } else if (isdigit((unsigned char) ch)) {
            printf(RED "%c" RESET, ch);
        } else {
            putchar(ch);
        }
    }
    return 0;
}

void skip(void) // skip whole words and still print them
{
    int c = getchar();
    while (!isspace((unsigned char) c) && c != EOF) {
        putchar(c);
        c = getchar();
    }
    if (isspace((unsigned char) c)) {
        putchar(c);
    }
}

这是一个示例程序输出。颜色没有显示在这里,所以我在终端上以红色突出显示的数字加上括号:

(1) this is a test testing123 (456) test
(2) second line test (3) (4) (5)
(3)rd line test
(4) (5) (6) (789)

【讨论】:

    【解决方案2】:

    首先停止使用全局变量。这是一个非常糟糕的习惯,使调试变得糟糕。这里finished 不需要是全局的。如果你让你的函数colorskip 得到一个char 作为参数,ch 就不再需要作为一个全局变量了。还可以使用定义的宏来为您提供系统值并使代码更具可读性(我认为EOF 而不是-1)

    其次,您是否熟悉调试工具(这里gdb 做得很好)。

    最后你的输入文件是否有结束字符?我的意思是你没有写你如何使用你的代码。但是你有2个选择。在第一种情况下,您向程序提供输入文件:./myprog &lt; my_file.txt。在另一种情况下,您手动输入程序。在这种情况下,您需要给他一个文件结尾字符(在大多数系统上为 Ctrl + D)。

    【讨论】:

      猜你喜欢
      • 2019-07-21
      • 1970-01-01
      • 1970-01-01
      • 2014-09-11
      • 1970-01-01
      • 2010-09-20
      • 2017-05-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多