【问题标题】:Dynamic memory allocation for char**char 的动态内存分配**
【发布时间】:2021-01-22 11:54:51
【问题描述】:

我正在尝试为字符串数组动态分配内存,但遇到了分段错误。如果你能告诉我一些方法来做到这一点,那将非常有帮助。

到目前为止,我的知识是 char* 是一个字符串,而 char** 是一个二维字符串数组。 如果 s[i] 是一个字符串,那么 *(s + i) 不应该也是一样的吗?我对这个话题感到困惑。

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

int n;

int main(void)
{
    scanf("%i", &n);  //number of strings being inputted

    char **s = calloc(n, 10 * (sizeof(char) + 1));

    for (int i = 0; i < n; i++) //get strings
    {
        fgets(*(s + i), 10 * (sizeof(char) + 1), stdin);
    }

    for (int i = 0; i < n; i++) //print the strings
    {
        fputs(*(s + i), stdout);
    }

    return 0;
}

【问题讨论】:

    标签: arrays c malloc c-strings calloc


    【解决方案1】:

    在此声明中

    char **s = calloc(n, 10 * (sizeof(char) + 1));
    

    您分配了一个内存,其地址分配给了指针s。由于调用函数calloc,内存被初始化为零。

    所以在这个声明中

    fgets(*(s + i), 10 * (sizeof(char) + 1), stdin);
    

    指针s 被取消引用,或者是空指针(因为指向的内存被零初始化)或者如果表达式s + i 指向分配的内存之外,则具有不确定的值。

    您需要分配char * 类型的指针数组,并为每个指针分配已分配字符数组的地址。

    通常您还应该检查callocmalloc 调用的返回值。

    您的代码还有另一个问题。打完scanf

    scanf("%i", &n);  //number of strings being inputted
    

    输入缓冲区包含新行字符'\n',它将被fgets 的以下调用读取。所以fgets 的第一次调用实际上会读取一个空字符串。

    另一个问题是您应该删除可以通过fgets 附加到读取字符串的换行符。例如,一些读取的字符串可以包含新的 .line 字符,而另一些可以不包含它,具体取决于用户键入的字符数。

    如果你想在不使用带指针的下标运算符的情况下编写程序,那么它可以查看例如以下方式。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(void) 
    {
        size_t n = 0;
        
        if ( scanf( "%zu", &n ) == 1 )
        {
            scanf( "%*[^\n]" );
            scanf( "%*c" );
        }
    
        char **s = NULL; 
        
        if ( n != 0 ) s = calloc( n, sizeof( char * ) );
        
        if ( s != NULL )
        {
            size_t len = 11;
            
            size_t m = 0;
            
            while ( m < n && ( *( s + m ) = malloc( len  * sizeof( char ) ) ) != NULL ) m++;
            
            size_t i = 0;
            
            while ( i < m && fgets( *( s + i ), len, stdin ) != NULL ) i++;
    
            m = i;
            
            for ( i = 0; i < m; i++ ) //print the strings
            {
                ( *( s + i ) )[ strcspn( *( s + i ), "\n" )] = '\0';
                // or without the subscript operator
                // *( *( s + i ) + strcspn( *( s + i ), "\n" ) ) = '\0';
    
                puts( *( s + i ) );
            }
            
            for ( i = 0; i < n; i++ ) free( *( s + i ) );
        }
        
        free( s );
        
        return 0;
    }
    

    程序输出可能看起来像

    10
    one
    two
    three
    four
    five
    six
    seven
    eight
    nine
    ten
    one
    two
    three
    four
    five
    six
    seven
    eight
    nine
    ten
    

    【讨论】:

      【解决方案2】:
      scanf("%i", &n);  //number of strings being inputted
      

      不完整,因为scanf(3) 可能会失败(例如,如果您的用户输入hello)。您需要添加更多代码检查 scanf 是否成功。我建议:

      if (scanf("%i", &n) < 0) {
        perror("number of strings expected");
        exit(EXIT_FAILURE);
      }
      

      然后

      char **s = calloc(n, 10 * (sizeof(char) + 1));
      

      错了。数组的每个元素都是一个char*,在大多数机器上都有 4 或 8 个字节(sizeof(void*))。

      所以换成

      char **s = calloc(n, sizeof(char*));
      

      或使用等效的char**s = calloc(n, sizeof(*s));,在我看来它的可读性较差。

      另外,calloc 可能会失败。你需要添加一个测试,比如

      if (s == NULL) {
         perror("calloc of s");
         exit(EXIT_FAILURE);
      }
      

      仔细阅读您未在this C reference 中编写的每个函数的文档(如callocfgets 等...)。

      下一步应该是循环填充s 的每个元素。另一个calloc,或者如果你的系统提供了一些电话strdup(3)。当然,calloc(或strdup)也可能失败,您需要针对失败进行测试。

      如果你的系统有getline(3)(甚至readline(3)),你也可以使用它。如果您在法律和技术上被允许使用它们,请咨询您的经理。

      如果您的编译器是 GCC,请使用所有警告和调试信息调用它:gcc -Wall -Wextra -g。一旦没有警告,请使用像 GDB 这样的调试器来了解可执行文件的行为。

      考虑也使用Clang static analyzer(在获得使用权限后)。

      在板子上(或纸上)绘制一个带有代表指针的箭头的图形(如维基百科上的 doubly linked-list 图形),以了解您的程序的行为。

      【讨论】:

      • ^^^ 特别是因为即使进行了这种更改,*(s+i) 也是一个空指针,fgets(*(s + i), 10 * (sizeof(char) + 1), stdin);fgets 撒谎,告诉它那里有一个大小为 11 个字符的缓冲区。
      • 我建议char **s = calloc(n, sizeof(*s));
      猜你喜欢
      • 2017-08-27
      • 2015-09-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-28
      • 2011-06-08
      相关资源
      最近更新 更多