Использование двойных указателей в C

Я выполняю задачу, в которой я должен использовать функцию getline для чтения ввода пользователя с терминала. Вот мой код:

int main(int ac, char **av)
{
        printf("Write something: \n");
        char **buf = NULL;
        size_t buf_size = 0;

        ssize_t bytes_read = getline(buf, &buf_size, stdin);
        if (bytes_read != -1)
                write(STDOUT_FILENO, buf,  buf_size);
        else
                printf("An error occured\n");
        free(buf);
        return (0);
}

Из кода выше. Моя программа отображала текст: Произошла ошибка. Сделал рефакторинг кода и вот что получилось:

int main(int ac, char **av)
{
        printf("Write something: \n");
        char *buf = NULL;
        size_t buf_size = 0;

        ssize_t bytes_read = getline(&buf, &buf_size, stdin);
        if (bytes_read != -1)
                write(STDOUT_FILENO, buf,  buf_size);
        else
                printf("An error occured\n");
        free(buf);
        return (0);
}

Вуаля! Код отображал все, что было введено, именно то, что я хотел. Итак, я исправил свою проблему. Но что я пытаюсь понять, так это то, что не так в первом фрагменте кода? Правильно ли это сделать?: char **name = "John Doe"; или char **name = NULL;

Я сделал несколько быстрых тестов на онлайн-компиляторе. Вот код:

int main() {
    // Write C code here
    char **name = "John Doe";
    printf("%p\n", name); //0x55f5f9890004
    printf("%p\n", *name); //0x656f44206e686f4a
    printf("%c\n", *name); //J
    printf("%p", "John Doe"); //0x55f5f9890004

    return 0;
}

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

Вам нужно включить предупреждения компилятора и прочитать их — тогда вы будете проинформированы о том, что такие вещи, как char **name = "John Doe";, на самом деле недопустимы, и вызовут неопределенное поведение в будущем.

UnholySheep 11.11.2022 10:19

@UnholySheep. Я использовал эти флаги при компиляции: gcc -Wall -Werror -pedantic . Но не получил ошибку

Amani 11.11.2022 10:35

(1) Недостаточно знать тип аргумента. Вам нужно прочитать документацию, чтобы понять, какое значение ожидается. (2) Аргумент-указатель со значением NULL, если он разрешен, обычно означает «это необязательный аргумент, и в этом случае я не предоставляю значимого значения; это фактически игнорируется». Может ли первый аргумент getline быть такого рода?

n. m. 11.11.2022 11:42

@н.м. Как подробно описано в документации man7.org/linux/man-pages/man3/getline.3.html, *first_argument(buffer) может быть NULL. Итак, что делает функция, так это то, что она выделяет память буферу

Amani 11.11.2022 12:00

Да, *first_argument может быть NULL. char **buf = NULL; так делает? Что такое *buf в данном случае?

n. m. 11.11.2022 12:03

@n.m Теперь я понял, о чем ты говоришь. Спасибо, что указали на это, кажется, я не очень интересовался чтением документации. (*buf будет недействительным, верно?)

Amani 11.11.2022 12:28

Да это правильно!

n. m. 11.11.2022 12:41
Шаблоны Angular PrimeNg
Шаблоны Angular PrimeNg
Как привнести проверку типов в наши шаблоны Angular, использующие компоненты библиотеки PrimeNg, и настроить их отображение с помощью встроенной...
Создайте ползком, похожим на звездные войны, с помощью CSS и Javascript
Создайте ползком, похожим на звездные войны, с помощью CSS и Javascript
Если вы веб-разработчик (или хотите им стать), то вы наверняка гик и вам нравятся "Звездные войны". А как бы вы хотели, чтобы фоном для вашего...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Начала с розового дизайна
Начала с розового дизайна
Pink Design - это система дизайна Appwrite с открытым исходным кодом для создания последовательных и многократно используемых пользовательских...
Шлюз в PHP
Шлюз в PHP
API-шлюз (AG) - это сервер, который действует как единая точка входа для набора микросервисов.
14 Задание: Типы данных и структуры данных Python для DevOps
14 Задание: Типы данных и структуры данных Python для DevOps
проверить тип данных используемой переменной, мы можем просто написать: your_variable=100
2
7
109
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

В случае char *bufgetline перейдет к ячейке памяти указателя buf и изменит этот адрес.

В случае char **buf = NULL;, getline перейдет в ячейку памяти "null" и попытается изменить этот адрес, что является нонсенсом. По сути, вы лжете getline и говорите ему, что сохранили указатель на него в ячейке памяти «null».

Что касается char **name = "John Doe";, это нарушение ограничений правил присваивания C, может случиться что угодно, поскольку код недействителен C. В том числе «кажется, что он работает нормально до тех пор, пока несколько лет спустя не произойдет таинственный сбой в рабочем коде». Возможно, вы захотите избавиться от этого онлайн-компилятора, например, применив Какие параметры компилятора рекомендуются для начинающих, изучающих C?

Ответ принят как подходящий

Когда вы хотите изменить переменную в функции, вам нужно передать ее по ссылке. В противном случае функция будет иметь дело с копией значения исходного объекта, используемого в качестве выражения аргумента, и изменение копии значения не повлияет на значение исходного объекта.

В C передача по ссылке означает косвенную передачу объекта через указатель на него.

Таким образом, разыменовав указатель, функция будет иметь прямой доступ к исходному объекту и сможет его изменить.

В этом фрагменте кода

    char *buf = NULL;                                                                                                                                       
    size_t buf_size = 0;                                                                                                                                    
                                                                                                                                                            
    ssize_t bytes_read = getline(&buf, &buf_size, stdin); 

вы хотите, чтобы указатель buf после вызова функции getline указывал на строку, прочитанную в функции. Поэтому вам нужно передать его в функцию по ссылке так же, как вы передаете другую переменную buf_size, значение которой также изменяется внутри функции, и вызывающая сторона функции должна знать результирующее значение переменной.

Если вы будете писать

    char **buf = NULL;                                                                                                                                       
    size_t buf_size = 0;                                                                                                                                    
                                                                                                                                                            
    ssize_t bytes_read = getline(buf, &buf_size, stdin);   

тогда функция внутри себя попытается разыменовать нулевой указатель buf, что приводит к неопределенному поведению, потому что она думает, что так называемый «двойной указатель» указывает на объект типа char *, который функция должна изменить.

Чтобы было понятнее, рассмотрим следующие демонстрационные программы.

#include <stdio.h>

void f( char *s )
{
    s = "World!";
}

int main( void )
{
    char *s = "Hello";

    printf( "Before calling f s = %s\n", s );

    f( s );

    printf( "After  calling f s = %s\n", s );
}

Вывод программы

Before calling f s = Hello
After  calling f s = Hello

Указатель s, переданный функции, будет значением. То есть функция имеет дело с копией значения указателя. Изменение копии не влияет на исходное значение указателя s.

Теперь рассмотрим следующую программу

#include <stdio.h>

void f( char **s )
{
    *s = "World!";
}

int main( void )
{
    char *s = "Hello";

    printf( "Before calling f s = %s\n", s );

    f( &s );

    printf( "After  calling f s = %s\n", s );
}

Вывод программы

Before calling f s = Hello
After  calling f s = World!

То есть, когда указатель s передается функции по ссылке, функция может изменить исходный указатель s, разыменовав указатель, переданный функции, которая указывает на исходный указатель s. Что касается этой декларации

char **name = "John Doe";

тогда компилятор должен выдать сообщение о том, что объявление неверно. Строковый литерал, используемый в качестве инициализатора, неявно преобразует указатель tp в его первый элемент типа char *. Но инициализированная переменная имеет тип char ** и неявного преобразования между этими типами указателей нет.

Также спецификатор преобразования %s ожидает аргумент типа char * вместо типа char **.

Итак, вы должны написать

char *name = "John Doe";

Или, поскольку вы не можете изменить строковый литерал, лучше написать объявление как

const char *name = "John Doe";

Спасибо за помощь. Я думал, что понял указатель, но, похоже, нет. Просто кое-что рассказал об указателе по ссылке и по значению.

Amani 11.11.2022 12:25

@ Амани Нет. Пожалуйста.:)

Vlad from Moscow 11.11.2022 15:02

В C нет «двойных указателей», поэтому вы не можете их использовать.

Но для любого типа T можно создать тип «указатель на T». И это тот случай, когда T также является типом указателя. Это указатель на указатель. Не "двойной указатель". Если вы видите char** как «двойной указатель», вы навсегда запутаетесь.

Ну есть double* :)

Lundin 11.11.2022 14:43

Двойной указатель должен ссылаться на действительный указатель для работы (поскольку этот указатель будет хранить ссылку на вновь выделенную память.

int main(int ac, char **av)
{
        printf("Write something: \n");
        char *realBuf = NULL
        char **buf = &realBuf;
        size_t buf_size = 0;

        ssize_t bytes_read = getline(buf, &buf_size, stdin);
        if (bytes_read != -1)
                write(STDOUT_FILENO, *buf,  buf_size);
        else
                printf("An error occured\n");
        free(*buf);
        return (0);
}

Другие вопросы по теме