Я выполняю задачу, в которой я должен использовать функцию 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. Не уверен, что мои выводы верны. Если бы вы могли дать лучшее объяснение вышеупомянутой основной функции, это было бы круто.
@UnholySheep. Я использовал эти флаги при компиляции: gcc -Wall -Werror -pedantic . Но не получил ошибку
(1) Недостаточно знать тип аргумента. Вам нужно прочитать документацию, чтобы понять, какое значение ожидается. (2) Аргумент-указатель со значением NULL, если он разрешен, обычно означает «это необязательный аргумент, и в этом случае я не предоставляю значимого значения; это фактически игнорируется». Может ли первый аргумент getline
быть такого рода?
@н.м. Как подробно описано в документации man7.org/linux/man-pages/man3/getline.3.html, *first_argument(buffer) может быть NULL. Итак, что делает функция, так это то, что она выделяет память буферу
Да, *first_argument
может быть NULL. char **buf = NULL;
так делает? Что такое *buf
в данном случае?
@n.m Теперь я понял, о чем ты говоришь. Спасибо, что указали на это, кажется, я не очень интересовался чтением документации. (*buf будет недействительным, верно?)
Да это правильно!
В случае char *buf
getline
перейдет к ячейке памяти указателя 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";
Спасибо за помощь. Я думал, что понял указатель, но, похоже, нет. Просто кое-что рассказал об указателе по ссылке и по значению.
@ Амани Нет. Пожалуйста.:)
В C нет «двойных указателей», поэтому вы не можете их использовать.
Но для любого типа T можно создать тип «указатель на T». И это тот случай, когда T также является типом указателя. Это указатель на указатель. Не "двойной указатель". Если вы видите char** как «двойной указатель», вы навсегда запутаетесь.
Ну есть double*
:)
Двойной указатель должен ссылаться на действительный указатель для работы (поскольку этот указатель будет хранить ссылку на вновь выделенную память.
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);
}
Вам нужно включить предупреждения компилятора и прочитать их — тогда вы будете проинформированы о том, что такие вещи, как
char **name = "John Doe";
, на самом деле недопустимы, и вызовут неопределенное поведение в будущем.