У меня есть следующая переменная
char* a[2][2] = {{"123", "456"}, {"234", "567"}};
Я хотел сослаться на него, используя другую переменную. Пока приведение работает, доступ к элементам b приводит к ошибке сегментации. Например.
char*** b = (char***) a;
printf("%s", b[0][0]); // Segmentation fault
printf("%s", a[0][0]); // "123"
Я заметил, что адреса b и a одинаковы, но различаются указанными индексами. Было бы полезно понять, почему адрес b[0][0] отличается от адреса a[0][0].
char* [2][2]
— это не char***
, а char**
. Вам не нужно приведение типов, чтобы получить указатель на него.
@IS4 char* [2][2]
это не char**
, даже близко.
char*** b
означает «b
— это указатель на указатель на указатель на char
». Давайте посмотрим на ту вещь, на которую указывает b
. Это должен быть указатель на указатель на char
. Можете ли вы показать мне такую вещь в вашей программе?
Хотя актерский состав работает... Нет, не так. Приведения используются для того, чтобы заставить один тип рассматриваться как другой. Если вы что-то сделаете не так, оно взорвется позже.
@5reep4thy Прочтите это: Правильное размещение многомерных массивов Там есть хорошее объяснение разницы. TLDR: указатель char ***
никогда не ссылается на «3D-массив», что бы вам ни говорили.
«Результат приведения не удался» Потому что вы лжете компилятору. Вы говорите ему, что b
— это указатель на указатель на указатель на char
, но в вашей программе нет ничего, на что b
можно было бы легально указывать. То есть компилятор думает, что в том месте, куда указывает b
, есть указатель на указатель на char, хотя на самом деле там совсем другое.
Не будьте 3-звездочным программистом : попробуйте 2 звезды: ideone.com/uboLN0
@pmg Обработка двумерного массива, как если бы это был одномерный массив, является неопределенным поведением.
@n.m.couldbeanAI Я не смог найти в Стандарте ничего конкретного о преобразовании 2D-массива в 1D-массив. Читаю C11 6.5.2.1 хотя вроде бы законно.
@pmg b[2]
за пределами поля. Вы рассматриваете *b
как массив из 4 элементов, но в программе такого нет. Есть 2 массива по 2 элемента в каждом. «Если операнд-указатель указывает на элемент объекта массива, и массив достаточно велик, результат указывает на смещение элемента от исходного элемента, так что разница нижних индексов результирующего и исходного элементов массива равна целочисленному выражению » (6.5.6). Массив недостаточно велик, поэтому UB. Тот факт, что сразу за этим массивом находится еще один массив, ничего не значит.
Да, @n.m.couldbeanAI, я согласен с твоей интерпретацией. Спасибо. Я по-прежнему буду «сглаживать» многомерные массивы, когда это будет удобно (но перестану делать это в SO)… и надеюсь, что мой код никогда не будет скомпилирован на DS9K :-)
Сначала давайте рассмотрим, как оператор индекса работает с массивом, объявленным следующим образом:
char* a[2][2] = {{"123", "456"}, {"234", "567"}};
Выражение a[0]
дает одномерный массив {"123", "456"}
типа char* [2]
.
То есть в этом выражении a[0]
указатель массива a
неявно преобразуется в указатель на его первый элемент типа char* (*)[2]
. Обозначим это как p
. И исходное выражение оценивается как *(p + 0)
, то есть *(char* (*)[2] + 0)
, то же самое, что *(char* (*)[2])
и, в свою очередь, то же самое, что char* [2]
.
Применив еще один индексный оператор к полученному одномерному массиву (первый элемент исходного двумерного массива), мы получим объект типа char*
.
Теперь давайте рассмотрим оператор индекса с указателем b
, объявленным следующим образом:
char*** b = (char***) a;
Указатель указывает на объем памяти, занимаемый массивом a
, где хранится указатель на элемент массива a
"123"
. То есть по этому адресу хранится объект типа char*
, указывающий на первый символ строкового литерала «123».
В этом случае выражение b[0]
получает значение этого указателя, и применив к этому значению еще один индексный оператор, вы получите символ '1'
строкового литерала. Это значение '1'
используется как указатель в этом вызове printf
:
printf("%s", b[0][0]);
Что приводит к неопределенному поведению. То есть этот вызов со спецификацией преобразования %s
пытается получить доступ к памяти, используя значение символа '1'
как указатель на действительный объект, а это не так.
Основное различие между использованием выражений с индексными операторами для массива a
и использованием выражений с индексными операторами для указателя b
заключается в том, что для массива не считывается память, занимаемая массивом. Вместо этого одно и то же значение адреса объема памяти, занимаемого указателем на строковый литерал "123"
, просто интерпретируется сначала как char * ( * )[2]
, а затем как char **
. В то время как для выражений с индексными операторами, примененными к указателю b
, считываются значения, на которые указывают выражения указателя char ***
и char **
.
Проясняет многие мои заблуждения :)
Чтобы индексировать char ***
, вам нужны указатели char **
, указывающие на указатели char *
.
char* a[2][2]
имеет только char*
указателей. Указатели char **
должны где-то существовать. Вам необходимо выделить память для указателей char **
.
#include <stdio.h>
int main() {
char* a[2][2] = {{"123", "456"}, {"234", "567"}};
char **b[2] = {a[0], a[1]};
char ***c = b;
printf("%s\n", a[0][0]);
printf("%s\n", b[0][0]);
printf("%s\n", c[0][0]);
}
Интересно, что, когда преобразования действительно работают, приведения не выполняются.
Если вы просто хотите ссылаться на a
как на другую переменную, вы можете объявить указатель на массив:
char *a[2][2] = {/* ... */}
char *(*b)[2] = a;
puts(a[0][0]);
puts(b[0][0]);
Причина, по которой простое использование char ***
не работает, заключается в том, что ожидается своего рода «массив указателей», где каждый элемент является указателем на отдельный блок, содержащий указатели на строки.
Но когда вы объявляете char *a[2][2]
, у вас есть массив 2 на 2 указателей непосредственно на строки. Вместо двух указателей на два отдельных массива строковых указателей у вас есть два смежных массива строковых указателей, один за другим в памяти.
Когда вы индексируете непрерывный многомерный массив, индексы используются для вычисления одного адреса в непрерывном выделении. Но когда у вас есть указатель на указатель, каждый элемент является указателем на новый массив, поэтому необходимы два разыменования. В первом случае на один уровень косвенности меньше.
Эта разница в расположении является причиной того, что вы не можете рассматривать многомерные массивы как указатели на указатели. Вы должны явно объявить указатель на массив, представляющий многомерный массив, который вы пытаетесь индексировать.
Касты всегда работают. Только когда что-то делаешь с результатом приведения, получается неудачно.