生成伪语言的 C 程序 - 全局 3D 数组太大(分段错误)?

C program to generate pseudolanguage - global 3D array is too large (segmentation fault)?

我应该编写一个程序,通过解析现有英文文本并查看打印的最后两个字母以确定下一个字母可能是什么(第一个被想象为'。' 和 ' ')。对于该任务,我想出了以下代码:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

short characters[256][256][256];

int main(int argc, char* argv[]){   
    if(argc<2){
        printf("In addition to the input file and maybe output file, please enter the number of output sentences as a command line argument.\n");
        return 1;
        }

    /*Different approach where I malloced the array instead, same result*/
    /*short ***characters=malloc(256 * sizeof(short**));
    for(int i=0; i<256; i++){
        *characters[i]=malloc(256 * sizeof(short*));
        for(int i2=0; i2<256; i++){
            characters[i][i2]=malloc(256 * sizeof(short**));
            }
        }*/

    /*Read text*/
    char a='.', /*pre-previous character*/
    b=' ', /*previous character*/
    c; /*current character*/
    int n=0;
    while((c=getchar())!=EOF){
        characters[a][b][c]++;
        a=b;
        b=c;
        n++;
        }

    /*Check how many sentences should be printed*/
    int sentences=0, multiplier=1;
    for(int i=0; i<sizeof(argv[1])/8; i++){
        sentences+=argv[1][i]*multiplier;
        multiplier*=10;
        }

    /*Print text*/
    int currentsentences=0, random, p1, p2;
    a='.';
    b=' ';
    while(currentsentences<sentences){
        int uninitialized;
        srand(time(0)+p1+p2+uninitialized); /*adds a bit of entropy*/
        random=rand()%n;
        p1=0;
        for(int i=0; ; i++){
            p2=p1+characters[a][b][i];
            if(random>p1 && random<=p2){
                c=characters[a][b][i];
                p1+=characters[a][b][i];
                break;
                }
            }
        putchar(c);
        if(c=='.' || c=='?' || c=='!')
            currentsentences++;
        a=b;
        b=c;
        }

    return 0;
    }

它编译时没有错误或警告,但是,当我尝试 运行 这个程序时,它总是 return 在打印任何内容之前出现段错误,除非我没有输入足够的命令行参数,在在这种情况下,它进入第一个 if 子句。这就是为什么我认为它必须对 3D 阵列做一些事情,因为它似乎甚至无法进入第一个循环(如果我让它在此之前打印一些东西,它就不会)。它需要那么大,因为结构如下:[pre-previous letter][previous letter][current letter]=how often did this constellation occur。因为我可能不需要更高的 ASCII 并且 char 的范围可能已经足够了,所以我尝试了 char 而不是 short 和 128*128*128 的数组 - 结果相同。 运行 它作为 root 并没有太大变化,增加 ulimit 也是如此。但是,全局变量不是保存在堆中吗?我在上面注释掉的 malloc() 的使用也没有改变任何东西。我在两台机器上试过这个,一台 OS:X,64 位和 8GB DDR3,另一台 Linux Mint 19.1,64 位和 32GB DDR4。两者结果相同(MacOS 表示 segmentation fault: 11,Linux 表示 segmentation fault (core dumped))。由于该阵列的已用内存约为 33 MB,因此我的 RAM 也不是问题所在。那么为什么会出现段错误呢?我是否需要为堆分配更多 RAM(我认为这甚至不可能)?它可能与数组 and/or 的大小无关吗?

这是程序的最新版本;仍然表现出相同的行为:

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

short characters[256][256][256];

int main(int argc, char* argv[]){   

    /*Check if number of sentences was given*/
    if(argc<2){
        printf("In addition to the input file and maybe output file, please enter the number of output sentences as a command line argument.\n");
        return 1;
        }

    /*Different approach with malloc*/
    /*short ***characters=malloc(256 * sizeof(short**));
    for(int i=0; i<256; i++){
        *characters[i]=malloc(256 * sizeof(short*));
        for(int i2=0; i2<256; i++){
            characters[i][i2]=malloc(256 * sizeof(short**));
            }
        }*/

    /*Read input text*/
    int a='.', /*pre-previous character*/
    b=' ', /*previous character*/
    c; /*current character*/
    int n=0;
    for(; (c=getchar())!=EOF; n++){
        characters[a][b][c]++;
        a=b;
        b=c;
        }

    /*Check how many sentences should be printed*/
    int sentences=0, multiplier=1;
    for(int i=strlen(argv[1])-1; i>=0; i--){
        sentences+=(argv[1][i]-'0')*multiplier;
        multiplier*=10;
        }

    /*Print text*/
    int currentsentences=0, random, p1=0, p2=0;
    a='.';
    b=' ';
    srand(time(0));
    while(currentsentences<sentences){
        random=(rand()+p1+p2)%n;
        p1=0;
        for(int i=0; i<256; i++){
            p2=p1+characters[a][b][i]; /*Determine range for character*/
            if(random>p1 && random<=p2){ /*Cheack if random number is in range of character*/
                c=characters[a][b][i];
                p1+=characters[a][b][i];
                break;
                }
            }
        putchar(c);
        if(c=='.' || c=='?' || c=='!')
            currentsentences++;
        a=b;
        b=c;
        }

    return 0;
    }

更新:它显示的一个有趣的行为是,如果您在程序的最开头添加类似 printf(„here“) 的内容,如果第一个 [=23],它将输出 „here“ =] 声明如果输入。但是,如果不是,程序将在打印任何内容之前 return 出现段错误。

更新 2:有趣的是,如果您不提供输入文件并手动输入所有内容,它不会 return 出现段错误,但也永远不会完成。

更新 3:程序现在可以运行了,见下文。对我造成的所有问题深表歉意,感谢您对我的帮助。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

unsigned int characters[128][128][128];

int main(int argc, char* argv[]){   
     /*Check if input file was given*/
    if(argc<2){
        printf("Please enter an input file as command line argument.\n");
        return 1;
            }

    /*Check for input file, open it*/
    FILE *fp=NULL;
    fp=fopen(argv[1], "r");
    if(!fp){
        printf("Error 404: Input file not found.\n");
        return 404;
        }

    /*Read input text*/
    int a='.';  /*pre-previous character*/
    int b=' ';  /*previous character*/
    int c;      /*current character*/

    while((c=fgetc(fp))!=EOF){
        if(c<127 && c>='\t'){ /*All characters from higher ASCII and system codes ignored. Still uses letters, digits and typical special characters and formatting characters.*/ 
            characters[a][b][c]++;
            a=b;
            b=c;
            }
        }
    fclose(fp);

    /*Check how many sentences should be printed*/
    unsigned int sentences;
    printf("How many sentences do you want to be printed? ");
    scanf("%d", &sentences);

    /*Print text*/
    unsigned int currentsentences=0, random, p1=0, p2=0, n;
    a='.';
    b=' ';
    srand(time(0));
    while(currentsentences<sentences){
        n=0;
        for(int i='\t'; i<127; i++){
            n+=characters[a][b][i];
            }
        random=(rand()+p1+p2+sentences+currentsentences+clock())%n;
        p1=0;
        for(int i='\t'; i<127; i++){    
            p2=p1+characters[a][b][i]; /*Determine range for character in combination with line 58*/
            if(random>=p1 && random<p2 && characters[a][b][i]!=0){ /*Check if random number is in range of character and that character occured in that combination*/
                c=i;
                printf("%c", c);
                characters[a][b][c]++; /*Experimental, language will change over time pseudo-randomly*/
                break;
                }
            p1+=characters[a][b][i];
            }
        if(c=='.' || c=='?' || c=='!')
            currentsentences++;
        a=b;
        b=c;
        }

    printf("\n");

    return 0;
    }

主要问题出在这部分代码:

    p1=0;
    for(int i=0; ; i++){
        p2=p1+characters[a][b][i];
        if(random>p1 && random<=p2){
            c=characters[a][b][i];
            p1+=characters[a][b][i];
            break;
        }
    }

在这里你不断递增 i 而不检查越界访问。你应该有这样的东西:

if (i >= 255) { // error handling ....};

另请注意,循环中的 p1 始终为零。

在这部分

random=(rand()+p1+p2)%n;

p1p2 未初始化,因此您最终可能会得到一个负数,这显然意味着您永远不会遇到 break 语句。换句话说 - 一个不断递增 i 的无限循环(这会导致越界访问)。

例如,我将代码更改为:

    for(int i=0; ; i++){
        printf("random=%d p1=%d a=%c b=%c i=%d", random, p1, a, b, i);

并得到如下输出:

...
random=-3 p1=0 a=. b=  i=42484 p2=0
random=-3 p1=0 a=. b=  i=42485 p2=0
random=-3 p1=0 a=. b=  i=42486 p2=0
random=-3 p1=0 a=. b=  i=42487 p2=0
...

注意 random 是负数,因此循环永远不会终止。

在您 post 下的评论中指出了警告、错误和一些非常好的建议。 注意事项

下面的注释语句似乎很清楚,

/*Check how many sentences should be printed*/

但我不清楚您的以下代码片段是如何实现的:

    int sentences=0, multiplier=1;
    for(int i=0; i<sizeof(argv[1])/8; i++){  
        sentences+=argv[1][i]*multiplier;
        multiplier*=10;
        }

因此,以下简短片段是对不同方法的建议:

// assume at minimum input of one legal filespec,  
// eg: .\filename.txt (Windows) or ./filename.txt (Linux)
int main(int argc, char *argv[])
{
    FILE *fp = NULL;
    int c = 0;
    int sentences = 0;

    if(argc<2)
    {
        printf("Minimum command line usage:  <name>.exe [pathFileName].  Program exiting.");
        getchar();
        return 0;
    }

    fp = fopen(argv[1], "r");
    if(fp)
    {
        c = fgetc(fp); 
        while(c) // will exit upon EOF (-1) Note c is int, not char
        {
            if( (c=='.') || (c=='?') || (c=='!') )
            {
                sentences++;
            }
        }
        fclose(fp);
    }
    else return 0;  //error, file not opened.

    /* rest of your code here */

    return 0;
}

select下一个字符的整个逻辑是错误的:

  • 在循环迭代 i 以检查 characters[a][b][i] 之后,代码将 c 发送到输出。在这一点上,c 要么是以前的一些代码遗留下来的,要么是 characters[a][b][i] 对于某些 i,这意味着它是在分析期间看到的三元组的计数——它不是应打印的字符代码。
  • 准备 p1p2 并将它们与随机数进行比较的代码是荒谬的。该代码应该在 [0, N) 中选择一个随机数,其中 N 是所有字符代码的 characters[a][b][i] 之和i 然后 select 字符代码 c 这样 c 在 [p1, p2) 中,其中 p1characters[a][b][i] 对于 0 ≤ i < cp2 的总和是 p1 + characters[a][b][c].