getline()函数的细分错误?

时间:2019-01-19 11:07:23

标签: c linux getline

我正在Linux的终端上学习C,并且我在文本上做了一个小游戏,所以一切正常(特别是我的大循环,如果玩家想重启游戏),我只是添加了一个转储“在开头输入您的伪”。

输入后它不起作用,它说

Segmentation Error , core dumped

这里是所有代码,最重要的是前几行,因为其余所有代码均运行正常:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h>
int main (void) {
  // déclaration des variables
  bool essais8  = false ;
  bool essais10 = false ;
  bool essais12 = false;
  bool essais14 = false;
  bool condition = true;
  bool condition_diff = false;
  bool condition_recommencer = false;
  bool condition_bonus1 = true;
  bool condition_bonus2 = true;
  bool condition_bonus3 = true;
  char* buffer;
  char* recommencer;
  char* pseudo ;
  size_t bufsize = 32;
  size_t characters;
  int difficulte;
  int sheitan = 0;
  int sheitan2 = 0;
  int a;
  int bonus1;
  int bonus2;
  int bonus3;
  int ecart;
  int essais;
  int nb_bonus2;
  srand(time(NULL));




  // on récupère le pseudo du joueur
   printf("Salut ! Quel est ton nom ? ");
  characters = getline(&buffer,&bufsize,stdin);
  pseudo = buffer;
// after this I have the segmentation errror




  while (condition == true) {   // Boucle de recommencement du Jeu
// here is the big game that was functioning perfectly before I add the pseudo option on top.
  }
  printf("Fin du programme \n");
  return(0);
}

4 个答案:

答案 0 :(得分:2)

您忘记初始化buffer。而且,您最好将printf(3)格式的控制字符串以\n结尾,或者在任何输入之前调用fflush(3)(因为stdio(3)被缓冲了)。

未初始化的指针包含垃圾。使用undefined behavior。成为scared

所以我的建议是编码

size_t bufsiz = 64;
char *buffer = malloc(bufsiz);
if (!buffer) { perror("malloc buffer"); exit(EXIT_FAILURE); };

及以后的

printf("Salut ! Quel est ton nom ?\n");
characters = getline(&buffer,&bufsize,stdin);

下一次在编译时启用所有警告和调试信息,因此如果使用with,则编译代码GCC gcc -Wall -Wextra -g。您会得到一些警告。

当然,请阅读How to debug small programsdebugging with GDB

在Linux上,根据您的特定情况,您可能会对使用readline(3)而不是getline(3)感兴趣。

请不要忘记阅读所使用的每个函数的文档(例如,here,如果它是标准的C语言)。

答案 1 :(得分:2)

getline()可以以两种不同方式使用:

  1. 读入调用方提供的缓冲区。
  2. 读取时分配内存。

要使用1.初始化传递的指针以指向有效内存,并传递有效内存的大小。

#define INITIAL_SIZE (42)

int main(void)
{  
  char * buffer = malloc(INITIAL_SIZE * sizeof *buffer);
  size_t bufsize = INITIAL_SIZE;
  ssize_t characters = getline(&buffer, &bufsize, stdin);
  if (-1 == characters)
  {
     /* handle error. */
  }
  else
  {
     /* use buffer */
  }

  free(buffer); /* Free buffer. */
}

要使用2.初始化传递给NULL的指针,并传递0作为大小。

int main(void)
{
  char * buffer = NULL;
  size_t bufsize = 0;
  ssize_t characters = getline(&buffer, &bufsize, stdin);
  if (-1 == characters)
  {
     /* handle error. */
  }
  else
  {
     /* use buffer */
  }

  free(buffer); /* Free buffer. */
}

注意:getline()返回ssize_t 不是 size_t

答案 2 :(得分:1)

'buffer'变量指向一些垃圾内存地址。您需要先使用“ malloc”函数分配必要的内存,或者将“缓冲区”设置为静态数组而不是指针。

答案 3 :(得分:0)

getline()POSIX.1 function,它将行读入动态分配的缓冲区,从而允许任何长度的行(仅受可用内存量限制)。它返回读取的字符数,如果没有更多输入或发生错误,则返回-1。

以下是一种使用模式示例:

    char   *line = NULL;
    size_t  size = 0;
    ssize_t len;

    while (1) {

        len = getline(&line, &size, stdin);
        if (len < 1)
            break;

        /* Do something with the input line */

    }

您随时可以使用以下方式释放动态分配的缓冲区

    free(line);
    line = NULL;
    size = 0;

您想要清除指向NULL且大小为零的指针的原因是那样,您不会意外尝试访问已释放的内存,但是您可以调用getline(&line, &size, handle)来读取更多行会简单地识别出它没有缓冲区,并会分配一个新的缓冲区。


如果您小心的话,可以以任何希望的方式操作动态数据。例如:

while (1) {
    char   *line = NULL;
    size_t  size = 0;
    ssize_t len;

    len = getline(&line, &size, stdin);
    if (len < 1) {
        free(line);
        break;
    }

    /* Do something with the contents of the line */

    free(line);
}

可以工作,但是速度会很慢,因为C库将对每次读取的行至少执行一次malloc()调用,并可能根据行的长度进行额外的realloc()调用。 / p>

按原样编写getline()的原因是,它允许对任意数量的行重用同一缓冲区。如果顺序读取文件,则可以重复使用相同的缓冲区。让我们看一个更复杂的示例:

#define _POSIX_C_SOURCE  200809L
#include <stdlib.h>
#include <locale.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>

int main(int argc, char *argv[])
{
    unsigned long  linenum;
    char          *line = NULL, *in, *out, *end;
    size_t         size = 0, n;
    ssize_t        len;
    FILE          *src;
    int            arg;

    if (!setlocale(LC_ALL, ""))
        fprintf(stderr, "Warning: Your C library does not support your current locale.\n");

    if (argc < 2) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s FILENAME [ FILENAME ... ]\n", argv[0]);
        fprintf(stderr, "\n");
        exit(EXIT_FAILURE);
    }

    for (arg = 1; arg < argc; arg++) {

        src = fopen(argv[arg], "r");
        if (!src) {
            fprintf(stderr, "%s: %s.\n", argv[arg], strerror(errno));
            free(line);
            exit(EXIT_FAILURE);
        }

        linenum = 0;

        while (1) {

            len = getline(&line, &size, src);
            if (len < 1)
                break;

            linenum++;

            /* First character in the line read: */
            in = line;
            out = line;

            /* Pointer to the end-of-string character on the line: */
            end = line + len;

            /* Skip all leading whitespace characters. */
            while (in < end && isspace((unsigned char)(*in)))
                in++;

            /* Character copy loop. */
            while (in < end)
                if (isspace((unsigned char)(*in))) {
                    /* Replace consecutive whitespace characters with spaces. */
                    *(out++) = ' ';
                    do {
                        in++;
                    } while (in < end && isspace((unsigned char)(*in)));
                } else {
                    /* Copy all other characters as-is. */
                    *(out++) = *(in++);
                }

            /* There may be a single space before out. Backtrack it, if so. */
            if (out > line && out[-1] == ' ')
                out--;

            /* Mark the end of the string at out. */
            *out = '\0';

            /* Calculate the new length, just for output purposes. */
            n = (size_t)(out - line);

            /* Print the line. */
            printf("%s: Line %lu: '%s' (%zu of %zd characters)\n",
                   argv[arg], linenum, line, n, len);

        }
        if (!feof(src) || ferror(src)) {
            fprintf(stderr, "%s: Read error.\n", argv[arg]);
            fclose(src);
            free(line);
            exit(EXIT_FAILURE);
        }
        if (fclose(src)) {
            fprintf(stderr, "%s: Error closing file: %s.\n",
                            argv[arg], strerror(errno));
            free(line);
            exit(EXIT_FAILURE);
        }
    }

    free(line);
    line = NULL;
    size = 0;

    return EXIT_SUCCESS;
}

如果我们将上述内容另存为 example.c ,然后使用例如gcc -Wall -O2 example.c -o example,然后运行提供文本文件名称作为参数的程序,例如./example example.c,它将输出类似

的内容
example.c: Line 1: '#define _POSIX_C_SOURCE 200809L' (31 of 33 characters)
example.c: Line 2: '#include <stdlib.h>' (19 of 20 characters)
example.c: Line 3: '#include <locale.h>' (19 of 20 characters)
example.c: Line 4: '#include <string.h>' (19 of 20 characters)
example.c: Line 5: '#include <stdio.h>' (18 of 19 characters)
example.c: Line 6: '#include <ctype.h>' (18 of 19 characters)
example.c: Line 7: '#include <errno.h>' (18 of 19 characters)
example.c: Line 8: '' (0 of 1 characters)
example.c: Line 9: 'int main(int argc, char *argv[])' (32 of 33 characters)

该程序的作用是简单地逐行读取每个指定的文件,删除每行上的任何前导和尾随空格,并将所有连续的空格合并为一个空格。较小的字符数是剩余(和显示)的字符数,较大的数字是从文件读取的原始字符数。

示例程序的其他注释(如果您感兴趣的话)

  • setlocale(LC_ALL, "")调用告诉您的C库使用用户区域设置(通常在LANGLC_ALL环境变量中定义)。该程序仅对当前语言环境使用的字符集使用字符类型定义(以确定哪些代码为“空白”),因此也可以通过setlocale(LC_CTYPE, "")将其限制于此。如果C库不支持当前语言环境,则调用将返回NULL。通常这是由于用户配置错误所致,因此,程序随后发出警告会很有用。

  • isspace()(以及is*()中定义的所有其他<ctype.h>函数)采用未签名的字符代码(或EOF)。由于char类型可以是有符号的或无符号的,因此在提供给函数之前,我们将字符显式转换为(unsigned char)。考虑一下我们只需要用这种方式处理的这个愚蠢的历史包.。

  • 由于line指向动态分配的内存缓冲区的开头,因此我们不能修改它(通过realloc()free()除外,然后将其设置为{{1 }})。如果我们确实对其进行修改,则随后使用该指针进行的任何NULLgetline()调用都可能会吓坏并导致程序崩溃,因为它们确实需要指针指向缓冲区的开头,而不仅仅是里面的某个地方。

  • 我喜欢使用指针(free())而不是索引。在这里,char *in, *out, *endin开始,一直上升到但不包括line,在此处line+len将字符串末尾nul getline()放入指示行尾。这就是为什么我也经常使用名为\0的指针来指向它的原因。 end也从out开始,但仅当字符排成一行时才增加。

    如果您想到一排字母拼字游戏,例如在拼字游戏中,line指向您将放置下一个瓷砖的位置,而out则指向您获得的下一个瓷砖。 / p>

  • ingetline()返回零或负值(或getdelim()返回NULL)时,这意味着没有更多数据要读取,或者操作因其他原因失败。

  • 循环后,fgets()检查输入流是否被完全读取而没有错误。恰恰相反,相反:只有在发生错误或未读取整个文件时,表达式才为真。

    如果我已将数据写入某个文件,例如(!feof(src) || ferror(src)),则通常在此测试之前进行FILE *dst测试。如果在将C库缓冲的最后一个数据写入文件时出错,则为true。

  • if (fflush(dst))关闭文件。我个人更喜欢验证其返回值,因为即使当前只能在非常特殊的情况下失败,但作为用户,我绝对希望知道OS在写入数据方面是否存在问题!测试基本上不花费任何费用,但对用户而言可能至关重要。在处理数据时,我不希望任何程序“忘记”告诉我有问题。我的数据对我很重要。

    • fclose(src)是安全的,什么也不做。 (此外,free(NULL)等效于realloc(NULL, size),因此,如果您初始化指向NULL的指针,则不需要初始的malloc,您可以将malloc(size)始终设置为所需的大小。)

我建议您使用上面的代码。您甚至可以在ltrace(realloc())下运行它,以查看实际执行了哪些标准C库调用及其结果。或在strace(ltrace ./example example.c下)查看系统调用(从进程到操作系统内核)。

例如,您可以添加“说”

strace ./example example.c

if (linenum == 7) { /* We skip line 7, and even destroy it! Muahhahah! */ free(line); line = NULL; size = 0; continue; } 行之后,查看文本文件的第七行会发生什么。 (它们被跳过,即使释放了缓冲区,也不会发生任何不良情况(因为linenum++开始了while循环主体的下一次迭代),因为下一个continue会动态分配新行

如果您决定要保留行的一部分副本,只需计算需要为字符串末尾nul(getline()添加一个的长度,为副本重复分配那么多字符( \0始终在C中;因此,malloc()等人确实获取要分配的字符数),sizeof (char) == 1数据,并添加终止nul。例如,

memcpy()

如果需要完整的字符串(直到字符串nul的结尾),则可以使用POSIX.1-2008 strdup()