2016-02-01 3 views
4

Выполняя некоторые исследования многомерных массивов в C и как они хранятся в памяти, я наткнулся на это: «Does C99 guarantee that arrays are contiguous?». Топ проголосовали ответ гласит, что «Кроме того, должна быть обеспечена возможность перебирать весь массив с (символ *)», а затем дает следующую «правильный» код:Итерация по двумерному массиву с помощью одного указателя char

int a[5][5], i, *pi; 
char *pc; 

pc = (char *)(&a[0][0]); 
for (i = 0; i < 25; i++) 
{ 
    pi = (int *)pc; 
    DoSomething(pi); 
    pc += sizeof(int); 
} 

плакат продолжающую сказать что «делать то же самое с (int *) было бы неопределенным поведением, потому что, как сказано, массива [25] из int не было».

Эта линия меня смущает.

Почему использование указателя char представляет собой допустимое/определенное поведение при подстановке его с помощью указателя целого числа?

Извините, если ответ на мой вопрос должен быть очевиден. :(

+0

Связанный ответ и прилагаемые комментарии обсуждают проблему довольно широко. –

ответ

3

Разница между использованием char* и int* это строгие правила наложения спектров: Если доступ к (&a[0][0])[6] (.. I E через int*), компилятор волен предполагает, что доступ [6] не оставляет массив в a[0]. Таким образом, можно предположить, что (&a[0][0]) + 6 и a[1] + 1 указывают на разные места в памяти, даже если они этого не делают, и соответственно переупорядочивают их обращения.

char* - это отличие, поскольку оно явно освобождено от правил строгой сглаживания: вы можете сделать что-нибудь в char* и манипулировать его битами через этот указатель, не вызывая неопределенное поведение.

+0

Итак, используя * both * '(& a [0] [0]) + 6' и' a [1] + 1', чтобы читать * и * записывать эти две ячейки памяти в функции, может привести к неправильному поведению, когда компилятор переупорядочивает эти обращения, не так ли? – Norman

+0

Может ли кто-то продумать, что значит для компилятора «изменить порядок доступа»? – kylemart

+1

@ ky1emart Рассмотрим этот код: 'int * foo = & a [0] [0]; a [1] [1] = 7; foo [6] = 42; printf («% d \ n», a [1] [1]); вы можете обнаружить, что этот код печатает «7» вместо ожидаемого «42», потому что: а) компилятор выпустил код, который сначала записывает 42, а затем 7 в память, так что 'printf()' читает более поздние 7 или b) компилятор выпустил код, который сначала записывает 7, затем читает его обратно, а затем перезаписывает его на 42. Оба варианта приводят к одинаковому непреднамеренные результаты, и никто не соблюдает последовательность событий, которые вы записали. – cmaster

0

Либо ИНТ указатель или указатель символ должен работать, но работа должна немного отличаться в этих двух случаях. Если предположить, sizeof(int) равно 4. pc += sizeof(int) перемещает указатель 4 байта вперед, но pi += sizeof(int) будет двигаться 4 раза по 4 байта вперед . Если вы хотите использовать указатель INT, вы должны использовать pi ++

EDIT:.. Сожалеем об ответе выше, используя указатель INT не соответствует C99 (хотя, как правило, практически работает) причина объясняется ну в исходном вопросе: указатель проходит через массив, который не определен в стандарте. Если вы используете указатель int, вы должны начать с a[0], w hich - это другой массив от a[1]. В этом случае указатель int a[0] не может юридически (четко определен) указывать на элемент a[1].

ВТОРОЙ EDIT: Использование char указатель является действительным, так как следующий причина дается оригинальный ответ:

массив в целом должен работать, когда дается MemSet, memmove или тетсру с размер. Также должно быть возможно выполнить итерацию по всему массиву с помощью (char *).

Из раздела 6.5.6 «Аддитивные операторы»

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

Так что это разумно.

+1

Это не проблема. Проблема заключается в том, является ли законным инкремент 'int *', который инициализируется 'a [0] [0]' 25 раз. Связанный ответ утверждает, что это не так. –

+0

@RSahu Спасибо, я обновил ответ. – xuhdev

+0

Спасибо за быстрый ответ + ревизии, но как указатель char разрешает проблему UB? В этом суть моего вопроса. – kylemart

1

Стандарт очень ясно, что если у вас есть:

int a[5]; 
int* p = &a[0]; 

Тогда

p += 6; 

вызывает неопределенное поведение.

Мы также знаем, что память, выделенная для 2D массива, такие как

int a[5][5]; 

должны быть смежными. Учитывая, что, если мы будем использовать:

int* p1 = &a[0][0]; 
int* p2 = &a[1][0]; 

p1+5 является юридическим выражением и учитывая расположение a, она равна p2. Следовательно, если мы будем использовать:

int* p3 = p1 + 6; 

почему это не эквивалентно

int* p3 = p2 + 1; 

Если p2 + 1 является юридическим выражением, почему p1 + 6 не является юридическим выражением?

От чисто педантичной интерпретации стандарта, используя p1 + 6, является причиной неопределенного поведения. Однако, возможно, что стандарт не подходит для решения проблемы, когда речь заходит о 2D-массивах.

В заключении

Из всех практических точек зрения, нет никаких проблем в использовании p1 + 6.
С чисто педантичной точки зрения использование p1 + 6 - это неопределенное поведение.

Смежные вопросы