Динамическое выделение памяти для char**

Я пытаюсь динамически выделить память для массива строк, но у меня возникает ошибка сегментации. Если вы можете показать мне несколько способов сделать это, это было бы очень полезно.

На данный момент мне известно, что 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;
}

person namelessking    schedule 22.01.2021    source источник


Ответы (2)


В этой декларации

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

вы выделили память, адрес которой присвоен указателю s. Память была инициализирована нулями из-за вызова функции calloc.

Итак, в этом заявлении

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

указатель s разыменован и либо является нулевым указателем (поскольку указанная память была инициализирована нулями), либо имеет неопределенное значение, если выражение s + i указывает за пределы выделенной памяти.

Вам нужно выделить массив указателей типа char * и присвоить каждому указателю адрес выделенного массива символов.

Также в целом вы должны проверить возвращаемое значение вызова calloc или malloc.

Есть еще одна проблема с вашим кодом. После звонка 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
person Vlad from Moscow    schedule 22.01.2021

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);
}

Внимательно прочитайте документацию каждой функции (например, calloc, fgets и т. д.), которую вы не написали в этой Ссылка на C.

Следующим шагом должен быть цикл, заполняющий каждый элемент s. С другим calloc или вызовом strdup(3) если ваша система это позволяет. Конечно, этот calloc (или strdup) тоже может дать сбой, и вам нужно протестировать его на предмет сбоя.

Вы также можете использовать getline(3) (или даже < a href="https://man7.org/linux/man-pages/man3/readline.3.html" rel="nofollow noreferrer">readline(3)), если он есть в вашей системе. Уточните у своего менеджера, разрешено ли вам использовать их по закону и техническим причинам.

Если ваш компилятор GCC, вызовите его со всеми предупреждениями и отладочной информацией: gcc -Wall -Wextra -g. Если у вас нет предупреждений, используйте отладчик, такой как GDB, чтобы понять поведение вашего исполняемого файла.

Рассмотрите возможность использования статического анализатора Clang (после получения разрешения на его использование).

Нарисуйте на доске (или на бумаге) фигуру со стрелками, обозначающими указатели (например, двухсвязный список рисунок на википедии), чтобы понять поведение вашей программы.

person Basile Starynkevitch    schedule 22.01.2021
comment
^^^ тем более, что даже с этим изменением *(s+i) является нулевым указателем, а fgets(*(s + i), 10 * (sizeof(char) + 1), stdin); лжет fgets, говоря ему, что там есть буфер размером до 11 символов. - person WhozCraig; 22.01.2021
comment
Я бы предложил char **s = calloc(n, sizeof(*s)); - person 0___________; 22.01.2021