在C中将字符串存储在char *中

时间:2014-04-22 19:25:37

标签: c linked-list

在下面的代码中,我希望你能看到我有一个char*变量,并且我想从文件中读取一个字符串。然后我想从函数中传回这个字符串。我对指针感到困惑,所以我不太确定我应该做些什么。

这样做的目的是将数组传递给另一个要搜索名称的函数。

不幸的是程序崩溃了,我不知道为什么。

char* ObtainName(FILE *fp)
{
    char* temp;
    int i = 0;

    temp = fgetc(fp);
    while(temp != '\n')
    {
        temp = fgetc(fp);
        i++;
    }
    printf("%s", temp);
    return temp;
}

非常感谢任何帮助。

4 个答案:

答案 0 :(得分:1)

fgetc会返回int,而非char*。此int是流中的字符,如果到达文件末尾,则为EOF

您隐含地将int强制转换为char*,即将其解释为地址(启用警告。)当您致电printf时,它会读取该地址并继续一次读取一个字符,寻找结束字符串的空终止符,但该地址几乎肯定无效。这是未定义的行为。

答案 1 :(得分:1)

我对你想要完成的事情采取了一些自由。而只需处理指针,只要设置最大长度,就可以使用固定大小的数组。我还包括了几个检查,以便您不会运行缓冲区的末尾或文件的末尾。同样重要的是确保在字符串末尾有一个空终止'\ 0'。

#define MAX_LEN 100
char* ObtainName(FILE *fp)
{
    static char temp[MAX_LEN];
    int i = 0;

    while(i < MAX_LEN-1)
    {
        if (feof(fp)) 
        {
            break;
        }
        temp[i] = fgetc(fp);
        if (temp[i] == '\n')
        {
            break;
        }
        i++;
    }
    temp[i] = '\0';
    printf("%s", temp);
    return temp;
}

答案 2 :(得分:1)

所以,这里有几个问题:

  1. 您没有为字符串内容留出任何存储空间;
  2. 您没有正确存储字符串内容;
  3. 您试图阅读不属于您的记忆;
  4. 您尝试返回字符串的方式会让您感到胃灼热。
  5. <强> 1。您没有为字符串内容留出存储空间

    该行

    char *temp;
    

    temp声明为指向char的指针;它的值将是单个字符值的地址。由于它在没有static关键字的情况下在本地范围内声明,因此其初始值将是不确定的,并且该值可能与有效的内存地址不对应。

    它不会为从fp读取的字符串内容留出任何存储空间;这必须作为一个单独的步骤完成,我将在下面介绍。

    <强> 2。您没有正确存储字符串内容

    该行

    temp = fgetc(fp);
    

    fp读取下一个字符并将其分配给temp。首先,这意味着您只存储从流中读取的最后一个字符,而不是整个字符串。其次,更重要的是,您将fgetc()(返回类型int的值)的结果分配给类型为char *的对象(被视为地址) )。你基本上是在说#34;我想对待这封信&#39; a&#39;作为记忆的地址。&#34;这带给我们......

    第3。您试图阅读不属于您的记忆

    在第

    printf("%s", temp);
    

    您尝试打印从temp中存储的地址开始的字符串。因为你写给temp的最后一件事很可能是一个值为&lt; 127,你告诉printf从非常低且很可能无法访问的地址开始,因此崩溃。

    <强> 4。您尝试返回字符串的方式可以保证让您心痛

    由于您已定义函数以返回char *,因此您需要执行以下操作之一:

    • 动态分配内存以存储字符串内容,然后将释放内存的责任传递给调用此内存的函数;
    • 使用static关键字声明一个数组,以便数组不会消失&#34;功能退出后;然而,这种方法有严重的缺点;
    • 更改功能定义;

    动态分配内存

    您可以使用动态内存分配例程为字符串内容预留一个存储区域,如下所示:

    char *temp = malloc( MAX_STRING_LENGTH * sizeof *temp );
    

    char *temp = calloc( MAX_STRING_LENGTH, sizeof *temp );
    

    ,然后在您撰写时返回temp

    malloccalloc都预留了您指定的字节数; calloc会将所有这些字节初始化为0,这会花费更多时间,但可以保存您的培根,尤其是在处理文本时。

    问题是当不再需要时,有人必须释放这个记忆;因为你返回指针,现在调用此函数的任何人都有责任在完成该字符串时调用free(),例如:

    void Caller( FILE *fp )
    {
      ...
      char *name = ObtainName( fo );
      ...
      free( name );
      ...
    }
    

    这扩展了程序内存管理的责任,增加了有人忘记释放内存的可能性,导致内存泄漏。 理想情况,您希望拥有相同的功能,可以将内存分配给它。

    使用静态数组

    您可以将temp声明为char数组并使用static关键字:

    static char temp[MAX_STRING_SIZE];
    

    这将在程序启动时在数组中留出MAX_STRING_SIZE个字符,并且会在调用ObtainName之间保留。完成后无需致电free

    方法的问题在于,通过创建静态缓冲区,代码不是重入;如果ObtainName调用了另一个函数,而该函数又调用了ObtainName,则该新调用将破坏之前缓冲区中的任何内容。

    为什么不将temp声明为

    char temp[MAX_STRING_SIZE];
    

    没有static关键字?问题是当ObtainName退出时,temp数组不再存在(或者更确切地说,它正在使用的内存可供其他人使用)。您返回的指针不再有效,并且可以覆盖该数组的内容,然后才能再次访问它。

    更改功能定义

    理想情况下,您希望ObtainName不必担心它必须写入的内存。实现这一目标的最佳方法是调用者将目标缓冲区作为参数传递,以及缓冲区的大小:

    int ObtainName( FILE *fp, char *buffer, size_t bufferSize )
    {
      ...
    }
    

    这样,ObtainName将数据写入调用者指定的位置(如果您想为不同目的获取多个名称,则非常有用)。该函数将返回一个整数值,可以是简单的成功或失败,也可以是一个错误代码,指示函数失败的原因等。

    请注意,如果您正在阅读文字,则不必逐字逐句阅读;您可以使用fgets()fscanf()等函数一次读取整个字符串。

    如果要读取以空格分隔的字符串,请使用fscanf(即,如果输入文件包含"This is a test"fscanf( fp, "%s", temp);将只读取"This")。如果要读取整行(由换行符分隔),请使用fgets()

    假设您想一次阅读一个单独的字符串,您可以使用以下内容(假设为C99):

    #define FMT_SIZE 20
    ...
    int ObtainName( FILE *fp, char *buffer, size_t bufsize )
    {
      int result = 1;  // assume success
      int scanfResult = 0;
    
      char fmt[FMT_SIZE];    
      sprintf( fmt, "%%%zus", bufsize - 1 ); 
    
      scanfResult = fscanf( fp, fmt, buffer );
      if ( scanfResult == EOF )
      {
        // hit end-of-file before reading any text
        result = 0;
      }
      else if ( scanfResult == 0 )
      {
        // did not read anything from input stream
        result = 0;
      }
      else
      {
        result = 1;
      }
    
      return result;
    }
    

    那么这个噪音是什么

    char fmt[FMT_SIZE];    
    sprintf( fmt, "%%%zus", bufsize - 1 ); 
    

    约?当您使用fscanf()%s转换说明符而没有最大长度说明符时,%[中存在非常讨厌的安全漏洞。 %s转换说明符告诉fscanf读取字符,直到它看到空白字符为止;如果流中有更多非空白字符而不是缓冲区大小要容纳,fscanf会将那些额外字符存储在缓冲区末尾,从而破坏跟随它的内存。这是一种常见的恶意软件攻击。所以我们要指定输入的最大长度;例如,%20s表示从流中读取不超过20个字符并将它们存储到缓冲区。

    不幸的是,由于缓冲区长度作为参数传递,我们不能写%20s之类的内容,而fscanf并没有给我们指定长度的方法作为fprintf的方式论证。所以我们必须创建一个单独的格式字符串,我们将其存储在fmt中。如果输入缓冲区长度为10,则格式字符串将为%10s。如果输入缓冲区长度为1000,则格式字符串将为%1000s

答案 3 :(得分:0)

以下代码扩展了您的问题中的代码,并返回已分配存储中的字符串:

char* ObtainName(FILE *fp)
   {
   int temp;
   int i = 1;
   char *string = malloc(i);
   if(NULL == string)
      {
      fprintf(stderr, "malloc() failed\n");
      goto CLEANUP;
      }
   *string = '\0';       

   temp = fgetc(fp);
   while(temp != '\n')
      {
      char *newMem;
      ++i;

      newMem=realloc(string, i);
      if(NULL==newMem)
         {
         fprintf(stderr, "realloc() failed.\n");
         goto CLEANUP;
         }
      string=newMem;
      string[i-1] = temp;
      string[i] = '\0';

      temp = fgetc(fp);
      }

CLEANUP:

   printf("%s", string);
   return(string);
   }

注意'free()'此函数返回的字符串,否则会发生内存泄漏。