Пожалуйста, взгляните на следующий код. Он пытается передать массив как char**
функцию:
#include <stdio.h>
#include <stdlib.h>
static void printchar(char **x)
{
printf("Test: %c\n", (*x)[0]);
}
int main(int argc, char *argv[])
{
char test[256];
char *test2 = malloc(256);
test[0] = 'B';
test2[0] = 'A';
printchar(&test2); // works
printchar((char **) &test); // crashes because *x in printchar() has an invalid pointer
free(test2);
return 0;
}
Тот факт, что я могу получить его только для компиляции, явно приведя &test2
к char**
уже намекам, что этот код неверен.
Тем не менее, мне интересно, что именно не так об этом. Я могу передать указатель на указатель на динамически размещенный массив, но не могу передать указатель на указатель для массива в стеке. Конечно, я легко могу обойти проблему, сначала назначив массив временной переменной, например так:
char test[256];
char *tmp = test;
test[0] = 'B';
printchar(&tmp);
Тем не менее, кто - то может объяснить мне , почему он не работает в гипсе , char[256]
чтобы char**
напрямую?
char (*)[256]
вchar**
?char**
. Без этого броска это не компилируется.test
является массивом, а не указателем, и&test
является указателем на массив. Это не указатель на указатель.Возможно, вам сказали, что массив является указателем, но это неверно. Имя массива - это имя всего объекта - всех элементов. Это не указатель на первый элемент. В большинстве выражений массив автоматически преобразуется в указатель на его первый элемент. Это удобство, которое часто полезно. Но есть три исключения из этого правила:
sizeof
.&
.В
&test
, массив является операндом&
, поэтому автоматического преобразования не происходит. Результатом&test
является указатель на массив из 256char
, который имеет типchar (*)[256]
, а неchar **
.Чтобы получить указатель на указатель на
char
fromtest
, сначала нужно создать указатель наchar
. Например:Еще один способ подумать об этом - понять, что
test
имена всего объекта - целого массива из 256char
. Он не называет указатель, поэтому в&test
нем нет указателя, адрес которого можно взять, поэтому он не может создатьchar **
. Для того чтобы создатьchar **
, вы должны сначала иметьchar *
.источник
_Alignof
упомянут оператор в дополнение кsizeof
и&
. Интересно, почему его убрали ..._Alignof
только принимает имя типа в качестве операнда и никогда не принимает массив или любой другой объект в качестве операнда. (Я не знаю почему; кажется, синтаксически и грамматически это может быть похожеsizeof
, но это не так.)Тип
test2
естьchar *
. Таким образом, тип&test2
будет ,char **
который совместим с типом параметраx
вprintchar()
.Тип
test
естьchar [256]
. Таким образом, тип&test
будет ,char (*)[256]
который не совместим с типом параметраx
вprintchar()
.Позвольте мне показать вам разницу с точки зрения адресов
test
иtest2
.Вывод:
Укажите, чтобы отметить здесь:
Выход (адрес памяти) из
test2
и&test2[0]
является численно одинаковы и их тип и тот же , которыйchar *
.Но есть
test2
и&test2
разные адреса и их тип тоже разный.Тип
test2
естьchar *
.Тип
&test2
естьchar **
.Выход (адрес памяти) из
test
,&test
и&test[0]
является числовым же , но их тип отличается .Тип
test
естьchar [256]
.Тип
&test
естьchar (*) [256]
.Тип
&test[0]
естьchar *
.Как показывает вывод, так
&test
же, как&test[0]
.Следовательно, вы получаете ошибку сегментации.
источник
Вы не можете получить доступ к указателю на указатель, потому что
&test
это не указатель - это массив.Если вы берете адрес массива, приводите массив и адрес массива к ним
(void *)
и сравниваете их, они (за исключением возможной педантизации указателей) будут эквивалентны.То, что вы действительно делаете, похоже на это (опять же, за исключением строгого алиасинга):
что совершенно очевидно неправильно.
источник
Ваш код ожидает аргумент
x
оprintchar
к точке в память, содержащий(char *)
.В первом вызове он указывает на хранилище, используемое для,
test2
и, таким образом, действительно является значением, указывающим на a(char *)
, последний указывает на выделенную память.Во втором вызове, однако, нет места, где
(char *)
можно было бы хранить какое-либо такое значение, и поэтому невозможно указать на такую память. Приведение к(char **)
вам добавило бы устранение ошибки компиляции (о преобразовании(char *)
в(char **)
), но это не привело бы к тому, что хранилище казалось бы пустым, чтобы содержать(char *)
инициализированный указатель на первые символы теста. Приведение указателя в C не меняет фактическое значение указателя.Чтобы получить то, что вы хотите, вы должны сделать это явно:
Я предполагаю, что ваш пример - это фрагмент кода гораздо большего размера; Например, возможно, вы хотите
printchar
увеличить(char *)
значение, на котороеx
указывает переданное значение, чтобы при следующем вызове был напечатан следующий символ. Если это не так, почему бы вам просто не передать(char *)
указание на символ, который будет напечатан, или даже просто передать сам символ?источник
char **
. Переменные / объекты массива - это просто массив с неявным адресом, который нигде не хранится. Нет дополнительного уровня косвенности для доступа к ним, в отличие от переменной-указателя, которая указывает на другое хранилище.Очевидно, что взятие адреса
test
такое же, как взятие адресаtest[0]
:Скомпилируйте это и запустите:
Таким образом, основной причиной ошибки сегментации является то, что эта программа будет пытаться разыменовать абсолютный адрес
0x42
(также известный как'B'
), который ваша программа не имеет разрешения на чтение.Хотя с другим компилятором / машиной адреса будут другими: попробуйте онлайн! , но вы все равно получите это по какой-то причине:
Но адрес, который вызывает ошибку сегментации, может очень отличаться:
источник
test
не совпадает с получением адресаtest[0]
. Первый имеет типchar (*)[256]
, а второй имеет типchar *
. Они не совместимы, и стандарт C позволяет им иметь разные представления.%p
он должен быть преобразован вvoid *
(опять же из соображений совместимости и представления).printchar(&test);
может произойти сбой для вас, но поведение не определено стандартом C, и люди могут наблюдать другое поведение в других обстоятельствах.&test == &test[0]
нарушает ограничения в C 2018 6.5.9 2, потому что типы несовместимы. Стандарт C требует реализации для диагностики этого нарушения, и результирующее поведение не определяется стандартом C. Это означает, что ваш компилятор может выдавать код, оценивающий их как равные, но другой компилятор может этого не делать.Представление
char [256]
зависит от реализации. Это не должно быть так же, какchar *
.Приведение
&test
типаchar (*)[256]
к типуchar **
приводит к неопределенному поведению.С некоторыми компиляторами он может делать то, что вы ожидаете, а с другими нет.
РЕДАКТИРОВАТЬ:
После тестирования с gcc 9.2.1 выясняется, что оно
printchar((char**)&test)
фактически передаетсяtest
как приведенное значениеchar**
. Это как если бы инструкция былаprintchar((char**)test)
. Вprintchar
функцииx
указатель на первый символ теста массива, а не двойной указатель на первый символ. Двойная де-ссылкаx
приводит к ошибке сегментации, потому что 8 первых байтов массива не соответствуют действительному адресу.Я получаю точно такое же поведение и результат при компиляции программы clang 9.0.0-2.
Это может рассматриваться как ошибка компилятора или как результат неопределенного поведения, результат которого может зависеть от компилятора.
Другое неожиданное поведение заключается в том, что код
Выход
Странное поведение таково
x
и*x
имеет одинаковое значение.Это вещь компилятора. Я сомневаюсь, что это определяется языком.
источник
char (*)[256]
зависит от реализации? Представлениеchar [256]
не имеет отношения к этому вопросу - это просто куча битов. Но, даже если вы имеете в виду, что представление указателя на массив отличается от представления указателя на указатель, это также пропускает точку. Даже если они имеют одинаковые представления, код OP не будет работать, потому что указатель на указатель может быть разыменован дважды, как это делается вprintchar
, но указатель на массив не может, независимо от представления.char (*)[256]
доchar **
принимается компилятором, но не дает ожидаемого результата, потому что achar [256]
не совпадает с achar *
. Я предположил, что кодировка другая, иначе это дало бы ожидаемый результат.char **
, поведение не определено, и что, в противном случае, если результат преобразован обратноchar (*)[256]
, он сравнивается равным исходному указателю. Под «ожидаемым результатом» вы можете подразумевать, что при(char **) &test
дальнейшем преобразовании в achar *
он сравнивается равным&test[0]
. Это не является маловероятным результатом в реализациях, которые используют плоское адресное пространство, но это не просто вопрос представления.char **
ischar *
), то поведение не определено. В противном случае конверсия определяется, хотя значение определяется только частично, согласно моему комментарию выше.char (*x)[256]
это не то же самое, чтоchar **x
. Причинаx
и*x
вывод одного и того же значения указателя в том, чтоx
это просто указатель на массив. Ваш*x
- это массив , и использование его в контексте указателя возвращается к адресу массива . Никакой ошибки компилятора там (или в том, что(char **)&test
делает), просто небольшая умственная гимнастика, необходимая, чтобы выяснить, что происходит с типами. (cdecl объясняет это как «объявить x как указатель на массив 256 char»). Даже использованиеchar*
для доступа к объектному представлениюchar**
не является UB; это может псевдоним что угодно.