【问题标题】:Char Arrays and Segmentation Fault字符数组和分段错误
【发布时间】:2016-05-25 17:48:37
【问题描述】:

C 和指针的新手,我不知道为什么这里有分段错误...这里的一些代码未使用,我只是想测试我的文字是否被读入正确并分配正确的空间量。最终我会有一个 char **,打印时的格式看起来像“cat the”、“new hey”、.....
在我的代码中,单词代表每个单独的单词

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

char *expand(char* source, int size);


char *expand(char *source, int size)
{
  char *expansion = (char *)malloc(size * sizeof(char));
  int i;
  for (i=0; i<size-1; i++)
    {
      expansion[i] = source[i];
    }
  free(source);
  return expansion;
}

int main(int argc, char **argv)
{
  int x;
  if (argc <= 2)
    {
      if (isdigit(*argv[1]))
        {
          x = atoi(argv[1]);
        }
    }
  else
    {
      fprintf(stderr, "Invalid Input\n");
      return 1;
    }


  //store pointers to each individual word
  char *words = (char *)malloc(3 * sizeof(char));
  int size = 3;//initial arbitrary size
  int count = 0;
  char *temp;
  while (1)
    {
      fscanf(stdin, "%s", temp);
      if (feof(stdin))
        {
          break; //break if end of file
        }
      if (count == size - 1)
        {
          size++;
          words = expand(words, size);
        }
      words[count] = *temp;
      count++;
    }

  int i;
  for(i=0; i<size-1; i++)
    {
      fprintf(stderr, "%s, ", words[i]);
    }
  fprintf(stderr, "\n");

  return 0;
}

【问题讨论】:

    标签: c arrays pointers char segmentation-fault


    【解决方案1】:

    你的麻烦直接跳出来的一个主要原因是你声明了char *temp;然后,在您的 while 循环中,开始尝试将数据读入指针指向的内存中......但是,您从来没有费心实际分配内存并将指针指向它。

    这是一个远非理想的示例,您应该使用它来修改char *wordschar *temp。我建议,在您熟悉如何正确管理动态内存(malloc/free)之前,先从静态声明开始:

    char words[512];
    int size = 3;
    int count = 0;
    char temp[512];
    words[0] = 0;
    temp [0] = 0;
    while...
    

    虽然像这样的“幻数”声明显然不明智或不安全,但它们使用一个数字(如 512)创建一个起点,该数字可能比您在测试时可能通过的任何基于行的输入都要大。一旦你让这个工作正常,然后回来再看看malloc

    考虑这个为argv[1] 中传递的理论参数分配内存的示例:

    char *ptr = NULL;
    ptr = malloc(strlen(argv[1] + 1);
    strcpy(ptr, argv[1]);
    
    ... Do lots of stuff ...
    free(ptr);
    

    请注意,分配的字符串长度大于为字符串的空终止符留出空间所需的字符串长度。

    您还可能感兴趣的是 strdup 函数,它将为您执行正确的分配,但您仍然必须记住在完成后将内存分配给 free

    【讨论】:

    • 您能否举例说明如何正确管理 char * 的动态内存?
    • 当然。添加了一个简单的示例。
    【解决方案2】:

    您的直接分段错误是由于:

    char *temp;
    ...
    fscanf(stdin, "%s", temp)
    

    其中temp 是一个未分配 字符指针。当你声明一个指针时,例如char *temp; 指针没有指向 任何东西。它只是一个空变量。对于一个指向你希望temp 工作的指针,它必须将地址保存到一个足够大小的内存块作为它的值。

    这样想。当您声明 int i; 时,您不希望 i 保留任何特定值,直到您为其分配值。 'i' 未初始化。指针也不例外。在您为其分配有效地址之前,您不会期望 char *temp; 指向任何东西。当您调用fscanf 并尝试将输入存储在temp - boom 指向的地址时,会出现段错误,因为不知道temp 到底指向哪里。这肯定不是分配到足够大小的内存块。

    除了直接的问题之外,很难遵循代码的逻辑,因为它的推理非常混乱。猜猜看,您的目标似乎是从命令行传递x 的值,然后最终将size 的该值用于expand 'size'stdin 读取的字数。相反,看起来您已经下注并刚刚分配了 size = 3; 并决定尝试让它为 3 个单词工作。不用放弃,做对了就不费力了。

    当您将作为字符串输入的数字作为argv[1] 转换为整数时,您可以使用该值创建一个包含那么多指针的可变长度数组。没有任何令人信服的理由来声明任意数量的指针,因为您输入的输入可能会告诉您要扩展多少字。此时也不需要为指针分配存储空间,因为这似乎是expand 的目的。

    您正在从标准输入读取 单词。只要它们是普通单词,您就可以简单地使用静态声明的缓冲区来保存从stdin 读取的单词。只要它们是字典中的单词,您就知道它们不会超过28-characters。因此,您只需要 29-characters+1 表示 nul-terminating 字符)即可在阅读时保存每个单词。[1]

    scanf 系列函数根据给定的格式字符串返回成功转换的次数。您不使用feof 来查找输入的结尾,而是检查fscanf 的返回并将您处理的字数限制为size。要清理您处理输入的方式(我已将 size 用于您的 x),您可以执行类似于以下的操作:

    enum { MAXC = 32 };  /* constant for static buffer - longest word 28 char */
    ...
    int main (int argc, char **argv) {
    ...
        int size;  /* validate numeric input */
        if ((size = isdigit (*argv[1]) ? atoi (argv[1]) : 0) <= 0) {
            fprintf (stderr, "error: invalid input. usage %s int > 0\n", argv[0]);
            return 1;
        }
        ...
        int count = 0, i = 0;
        char *words[size];          /* array of 'size' pointers to char */
        char temp[MAXC] = {0};      /* buffer to hold each word input   */
    
        /* read at most 'size' words from stdin */
        while (size-- && fscanf (stdin, "%s", temp) == 1)
            words[count++] = expand (temp, strlen (temp));  /* expand  */
    

    查看声明的每个部分并阅读循环。 (在设置size 时了解三元 运算符的使用——在许多情况下它是一个有用的快捷方式)请注意您如何有两个条件来读取size &gt; 0fscanf (stdin, "%s", temp) == 1。如果您读取了size 或没有其他输入要读取,则循环终止。

    清理的其余部分相当简单。但是请注意,不需要使用可变参数 fprintf 来简单地将换行符打印到 stderr(例如 fprintf (stderr, "\n");)。只需打印单个字符(例如fputc ('\n', stderr);)。

    此外,在您分配内存时, (1) 保留一个指向内存块开头的指针,以便它可以(2) 在不再需要时释放。始终,始终在 Linux 上使用像 valgrind 这样的内存/错误检查程序来验证您是否正确使用了内存,并且在不再需要时所有块都已被释放。每个操作系统都有类似的程序,并且它们易于使用。没有理由不这样做。

    将所有部分放在一起,您可以执行以下操作:

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <ctype.h>
    
    enum { MAXC = 32 };  /* constant for static buffer - longest word 28 char */
    
    char *expand (char *source, size_t size);
    
    int main (int argc, char **argv) {
    
        if (argc != 2) {    /* validate number of arguments */
            fprintf (stderr, "error: insufficient input.\n");
            return 1;
        }
    
        int size;  /* validate numeric input */
        if ((size = isdigit (*argv[1]) ? atoi (argv[1]) : 0) <= 0) {
            fprintf (stderr, "error: invalid input. usage %s int > 0\n", argv[0]);
            return 1;
        }
    
        int count = 0, i = 0;
        char *words[size];          /* array of 'size' pointers to char */
        char temp[MAXC] = {0};      /* buffer to hold each word input   */
    
        /* read at most 'size' words from stdin */
        while (size-- && fscanf (stdin, "%s", temp) == 1)
            words[count++] = expand (temp, strlen (temp));  /* expand  */
    
        for (i = 0; i < count; i++) {       /* output each string read */
            char *fmt = i ? ", %s" : "%s";
            fprintf (stderr, fmt, words[i]);
        }
        fputc ('\n', stderr);
    
        for (i = 0; i < count; i++)         /* free allocated memory   */
            free (words[i]);
    
        return 0;
    }
    
    char *expand (char *source, size_t size)
    {
        char *expansion = calloc (1, size * sizeof *expansion + 1);
        size_t i;
    
        if (!expansion) {   /* validate memory allocation */
            fprintf (stderr, "error: virtual memory exhausted.\n");
            exit (EXIT_FAILURE);
        }
    
        for (i = 0; i < size; i++)
            expansion[i] = source[i];
    
        return expansion;
    }
    

    (注意:calloc 在上面使用是为了避免 valgrind 的某些版本中的怪癖。根据版本的不同,它可能会抱怨在 if(..) 语句中未初始化使用 expansion。这不是一个错误,但在旧版本中是一个怪癖。为确保您没有遇到该问题,我使用calloc 而不是malloc,它将所有新内存初始化为零并避免警告。注意: 它还确保了 nul-terminationexpansion 没有明确的 expansion[size] = 0; 在循环之后。)

    示例输入

    $ cat ../dat/captnjack.txt
    This is a tale
    Of Captain Jack Sparrow
    A Pirate So Brave
    On the Seven Seas.
    

    输出

    $ ./bin/expansion 5 < ../dat/captnjack.txt
    This, is, a, tale, Of
    
    $ ./bin/expansion 4 < ../dat/captnjack.txt
    This, is, a, tale
    

    内存/错误检查

    $ valgrind ./bin/expansion 5 < ../dat/captnjack.txt
    ==12849== Memcheck, a memory error detector
    ==12849== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
    ==12849== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
    ==12849== Command: ./bin/expansion 5
    ==12849==
    This, is, a, tale, Of
    ==12849==
    ==12849== HEAP SUMMARY:
    ==12849==     in use at exit: 0 bytes in 0 blocks
    ==12849==   total heap usage: 5 allocs, 5 frees, 18 bytes allocated
    ==12849==
    ==12849== All heap blocks were freed -- no leaks are possible
    ==12849==
    ==12849== For counts of detected and suppressed errors, rerun with: -v
    ==12849== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
    

    如果您有任何问题,请告诉我。我试图尽可能地猜测你的代码要去哪里。如果我错过了标记,请告诉我,我很乐意提供进一步的帮助。

    脚注 1. - 'Antidisestablishmentarianism' 是未删节词典中最长的词。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-19
      • 2019-01-28
      • 2017-02-25
      • 1970-01-01
      相关资源
      最近更新 更多