初学者如何处理SEGFAULT-s?(C)

slwdgvem  于 2023-01-20  发布在  其他
关注(0)|答案(1)|浏览(196)

我在SPOJ上遇到了一个问题,截止日期很快就到了。
我的任务是编写一个程序来计算给定行中有多少个标识符,标识符定义为一个字符序列,该序列来自集合'a'-'z''A'-'Z''0'-'9''_',从任意字母或下划线字符('_')开始。

    • 输入**

给定一定数量的数据集,每个数据集是由一定数量的单词序列组成的一行,单词之间用空格隔开,并以行尾字符结束(甚至最后一行)。一个单词是代码从33到126的任何ASCII字符的序列(更多细节请参见http://www.asciitable.com),例如aqui28$-3q_dat_。第二个字是标识符,但第一个不是。

    • 产出**

每行中标识符的数目。

    • 示例**

输入:

Dato25 has 2 c-ats and 3 _dogs
op8ax _yu _yu67 great-job ax~no identifier.

输出:

4
3

我编写的代码可以编译,但在提交时返回SIGSEGV(分段错误)。

#include <stdio.h>
#include <string.h>
struct WORDS
{
    char word[50][300]; // first time using 2 dimensional arrays, this may be causing the error
    int ident_count;
};
int is_identifier(int k, char ch[k])
{
    if (!((ch[0] >= 'A' && ch[0] <= 'Z') || (ch[0] >= 'a' && ch[0] <= 'z') || ch[0] == '_'))
        return 0;
    for (int a = 1; a < k; a++)
        if (!((ch[a] >= 'A' && ch[a] <= 'Z') || (ch[a] >= 'a' && ch[a] <= 'z') || (ch[a] >= '0' && ch[a] <= '9') || ch[a] == '_'))
            return 0;
    return 1;
}
int main()
{
    char c;
    int i = 0, j = 0, k = 0; // using i for lines, j for words in those lines, k for letters.
    struct WORDS line[100];
    while ((c = getchar()) != EOF)
    { // fgetc reads a single character
        if (c != ' ' && c != '\n')
        {
            line[i].word[j][k++] = c; // assign to each line's words'
            // printf("%c ", line[i].word[j][k]);
        }
        if (c == ' ')
        {
            line[i].ident_count += is_identifier(k, line[i].word[j]);
            j++; 
            k = 0; // new word will begin so reset the counter
        }
        if (c == '\n')
        {
            line[i].ident_count += is_identifier(k, line[i].word[j]);
            printf("%d\n", line[i].ident_count);
            i++;
            j = 0;
            k = 0;
        }
    }
    return 0;
}
pgvzfuti

pgvzfuti1#

您的代码展示了一个非常常见的反模式:在处理数据之前不必要地将大块数据读入内存,而实际上可以边处理边处理。
考虑一下:当你处理一个输入的单词时,你需要参考同一行或其他行的前一个或后一个单词吗?不需要。那么你为什么要在内存中保留所有这些呢?
实际上,除了刚刚读到的单个字符外,您不需要存储 * 任何 * 单词的 * 任何 * 部分。

  • 径迹
  • 到目前为止在当前行上看到了多少个标识符
  • 在任何给定时间解析的是什么类型的内容(可能的标识符、非标识符单词或空格),
  • 为每个读取的字符适当地更新,以及
  • 在每一行的末尾发出适当的输出(基于前面的)。

这可能比你的方法更快,它肯定会使用更少的内存,特别是更少的堆栈内存,而且它几乎没有为任何类型的边界溢出或无效指针使用提供空间,这是内存错误(如segfault)的常见原因。
至于为什么你原来的程序segfaults,我是用valgrind运行的,这是一个流行的识别内存使用问题的工具,它可以检测内存泄漏,一些越界访问,以及使用未初始化的内存。除此之外。它告诉我你从来没有初始化任何line[i]ident_count。非static局部变量(如line)不会自动初始化为任何特定的值,有时您可能会遇到这种情况,但这并不是您的特定问题的原因,但请培养良好的编程实践:修好它。
Valgrind没有向我指出任何其他错误,但是,您的程序segfault也没有向我指出示例输入。尽管如此,我预计,如果向您的程序输入超过100行和/或一行超过300个单词,我可能会对程序造成各种破坏。和/或一个单词中超过50个字符。自动判断倾向于包括探索问题空间极端的测试用例,所以你需要确保你的程序对 * 所有 * 有效输入都有效。
或者,在注解中提出了一个有效的观点,即您正在堆栈上分配一个大对象,而在法官的测试环境中堆栈空间可能不足以容纳它。如果这是问题所在,那么在当前代码中解决它的一个快速而简单的方法是只分配一个struct WORDS,并在每一行中重用它。这将使堆栈使用减少大约100倍。再说一次,同时将所有行存储在内存中有什么作用呢?

相关问题