Путаница с указателем переменной 2D-массива - PullRequest
5 голосов
/ 25 декабря 2011

У меня есть базовые сомнения в 2D массивах (язык C). Рассмотрим объявление двумерного массива следующим образом

int array[3][5];

Теперь, когда я делаю следующее, вывод обоих th ниже printf одинаков:

printf("%u\n", array);
printf("%u\n", *(array));

Теперь, когда попробуйте следующее:

printf("%u\n", array+1);
printf("%u\n", *(array)+1);

Выходы разные. Я получаю, что второй printf ссылается на массив [0] [1], а первый на массив [1] [0]. Как это работает? массив является указателем на что?

Заранее спасибо

Ответы [ 5 ]

5 голосов
/ 25 декабря 2011

Я постараюсь дать вам технически правильное объяснение, чтобы вы знали, что происходит.Не очень сложно, но на самом деле нелогично.

Intro:

В C есть «lvalues», которые в основном представляют «присваиваемые» объекты, которые имеют место где-то впамять и «rvalues», которые представляют собой «концептуальные» значения (не требуется размещать их где-либо конкретно).

Например, если вы определите int a = 5;, тогда a будет lvalue типаint и значение 5. Также его можно интерпретировать как (или, скорее, преобразовать в) значение типа int.Такое значение r будет по-прежнему известно как 5, но оно больше не будет содержать информацию о местоположении a в памяти.

Некоторым выражениям требуются значения l (например, слева от оператора= потому что вы должны присваивать объекту), а некоторым нужны значения r (например, operator +, потому что вам нужны только интегральные значения при добавлении или правая часть оператора =).Если выражению требуется значение rvalue, но вы передаете значение lvalue, оно преобразуется в значение rvalue.

Кроме того, в функции в C передаются только значения rval (что означает, что C является строго вызовом по значению, а неcall-by-reference).

Некоторые примеры:

int a = 1;
a; // a is an lvalue of type int and value 1
a = a+3; // here the `a` is converted to an rvalue of type int and value 1, then after the addition there's an assignment, on the lhs there's an lvalue `a` and an rvalue `4`

Преобразование из lvalue в rvalue обычно тривиально и незаметно (это похоже на взятие числа 5 с полки с надписью * 1021).*).Массивы в основном являются исключением.

Самое важное: В C нет значений типа массива.Существуют указатели lvalue и rvalues, целочисленные lvalue и rvalues, структурные lvalues ​​и rvalues ​​и т. Д. Но только массивы lvalue.Когда вы пытаетесь преобразовать lvalue типа массива в rvalue, у вас больше нет массива, у вас есть указатель на первый член массива.Это корень путаницы с массивами в C (и C ++).

Объяснение:

  • array
  • *(array)
  • array+1
  • *(array)+1

array - это lvalue типа int[3][5] (массив из 3-х или 5-ти).Когда вы пытаетесь передать его в функцию, он получает указатель типа int (*)[5] (указатель на массив из 5 дюймов), потому что это то, что осталось после преобразования lvalue-в-значение.

*(array) - обманщик,Сначала выполняется значение lvalue-to-rvalue, в результате чего получается rvalue типа int(*)[5], затем operator* принимает это значение rvalue и возвращает значение lvalue типа int[5], которое затем вы пытаетесь передать функции.Следовательно, он снова преобразуется в значение r, в результате чего int*.

array+1 приводит к преобразованию массива в значение типа int(*)[5], а значение rvalue увеличивается на единицу, поэтому (согласноправила арифметики указателя) указатель перемещается на 1 * sizeof(int[5]) байтов вперед.

*(array)+1: см. 2 пункта ранее, но конечное значение типа int* увеличивается, опять же по правилам арифметики указателя, на1 * sizeof(int).

Никаких загадок здесь!

2 голосов
/ 25 декабря 2011

2D-массивы в C сбивают с толку.
array и * array - это один и тот же указатель, но не одного типа.
array имеет тип int [3] [5] (который является массивом,размером 5, из массивов int [3].
* массив - это первая строка массива, которая имеет тип int [3].
массив + 1 означает массив плюс один элемент.Элемент массива имеет тип int [3], поэтому он вперед на 12 байт.
* массив + 1 означает * массив плюс один элемент.Элемент массива * имеет тип int, поэтому он на 4 байта вперед.

1 голос
/ 25 декабря 2011

Массивы не указатели.Игнорируйте любой ответ, книгу или учебное пособие, в которых говорится иначе.

Выражение типа массива, в большинстве случаев, преобразуется (во время компиляции) в указатель на массивпервый элемент.Исключения составляют:

  • Операнд sizeof (sizeof arr возвращает размер массива, а не размер указателя)
  • Операнд унарного &(&arr возвращает адрес массива, а не его первого элемента - та же ячейка памяти, другой тип).Это особенно относится к вашему примеру.
  • Строковый литерал в инициализаторе, используемый для инициализации объекта массива (char s[6] = "hello"; не копирует адрес строкового литерала, он копирует его значение)

2-мерный массив - это не что иное, как массив массивов.Существуют другие структуры данных, которые можно использовать с тем же синтаксисом x[y][z], но они не являются истинными 2-мерными массивами.Ваш.

Оператор индексирования [] определяется в терминах арифметики указателей.x[y] означает *(x+y).

Поведение вашего кода следует из этих правил.

Прочтите раздел 6 comp.lang.c FAQ .Это лучшее объяснение всего, что я видел.

И не используйте "%u" для печати значений указателей;преобразовать в void* и использовать "%p".

printf("%p\n", (void*)array);
printf("%p\n", (void*)*(array));
0 голосов
/ 25 декабря 2011

Шаг приращения в указателях соответствует размеру типа данных.

0 голосов
/ 25 декабря 2011

Вы можете понять таким образом:

массив указывает на 3 строки по 5 столбцов в каждой

когда вы делаете массив + 1, строка изменяется, и вы переходите к 1-й строке. Вы должны попытаться получить доступ с помощью * (массив + 1).

когда вы делаете * (массив), вы указываете на 0-ю строку, а * (массив) +1 перемещается вперед в столбце, поэтому элемент является массивом [0] [1]

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...