非常复杂的C无限循环调试

Very complex C infinite loop debugging

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

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

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);
    }
}

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

其次您是否熟悉调试工具(这里 gdb 可以很好地完成这项工作)。

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

无限循环

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

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

头文件

发布的代码缺少头文件。至少需要 #include <stdio.h>#include <stdbool.h>。显然需要 stdio.h,但也需要 stdbool.h,因为这是定义 truefalse 宏的地方。 _Bool 类型仍然可以使用,但最好使用 stdbool.h.

中定义的 typedef bool

函数声明

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

A​​NSI 转义码

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

warning: non-ISO-standard escape sequence, '\e'

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

逻辑问题

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

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(),在应该打印none时打印一个字符。一种解决方案是将 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() 函数,因为这个单行函数可以简单地手动内联。

不需要使用 printf()putchar() 就可以了。

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

最后,最好使用ctype.h中的函数来检查一个字符是数字,还是字母字符,还是白色space字符。与发布的代码中的直接比较相比,这更便携,更不容易出错。通过在 skip() 函数中使用 isspace(),会自动检查 \n 字符,避免了之前忘记测试行尾的问题。这也处理其他白色 space 字符,例如 \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)