Арифметика и массивы указателей: что на самом деле законно? - PullRequest
4 голосов
/ 05 марта 2010

Рассмотрим следующие операторы:

int    *pFarr, *pVarr;

int    farr[3] = {11,22,33};
int    varr[3] = {7,8,9};

pFarr = &(farr[0]);
pVarr = varr;

На этом этапе оба указателя указывают на начало каждого соответствующего адреса массива.Для * pFarr мы сейчас смотрим на 11, а для * pVarr - 7.

Точно так же, если я запрашиваю содержимое каждого массива через * farr и * varr, я также получаю 11 и 7.

Пока все хорошо.

Теперь давайте попробуем pFarr++ и pVarr++.Отлично.Сейчас мы смотрим на 22 и 8, как и ожидалось.

Но сейчас ...

Пытаясь подняться на farr++ и varr++ ... и мы получим "неправильный тип"аргумента для приращения ".

Теперь я распознаю разницу между указателем массива и обычным указателем, но поскольку их поведение схоже, почему это ограничение?

Это еще больше сбивает меня с толкукогда я также считаю, что в той же программе я могу вызывать следующую функцию якобы правильным и другим неправильным образом, и я получаю такое же поведение, хотя в отличие от того, что произошло в коде, размещенном выше!?

working_on_pointers ( pFarr, farr );  // calling with expected parameters
working_on_pointers ( farr, pFarr );  // calling with inverted parameters 

.

void working_on_pointers ( int *pExpect, int aExpect[] ) {

    printf("%i", *pExpect);  // displays the contents of pExpect ok
    printf("%i", *aExpect);  // displays the contents of aExpect ok

    pExpect++;               // no warnings or errors
    aExpect++;               // no warnings or errors

    printf("%i", *pExpect);  // displays the next element or an overflow element (with no errors)
    printf("%i", *aExpect);  // displays the next element or an overflow element (with no errors)

}

Может ли кто-нибудь помочь мне понять, почему указатели и указатели массивов ведут себя одинаково в одних контекстах, но отличаются в других?

Огромное спасибо.

РЕДАКТИРОВАТЬ: Новички, как я, могли бы дополнительно извлечь выгоду из этого ресурса: http://www.panix.com/~elflord/cpp/gotchas/index.shtml

Ответы [ 6 ]

8 голосов
/ 05 марта 2010

Разница в том, что для farr++, чтобы иметь какой-либо эффект, где-то компилятор должен был бы сохранить, что farr будет вычислять адрес второго элемента массива. Но нет места для этой информации. Компилятор выделяет место только для 3 целых чисел.

Теперь, когда вы объявляете, что параметр функции является массивом, параметр функции не будет массивом. Параметр функции будет указателем. В Си нет параметров массива. Поэтому следующие два объявления эквивалентны

void f(int *a);
void f(int a[]);

Не имеет значения, какое число вы ставите в скобках - поскольку параметр действительно будет указателем, «размер» просто игнорируется.

То же самое для функций - следующие два эквивалентны и имеют указатель функции в качестве параметра:

void f(void (*p)());
void f(void p()); 

Хотя вы можете вызывать как указатель на функцию, так и функцию (поэтому они используются аналогично), вы также не сможете писать в функцию, потому что это не указатель - он просто преобразует на указатель:

f = NULL; // error!

Почти так же, как вы не можете изменить массив.

5 голосов
/ 05 марта 2010

В C вы не можете назначить массивы. Итак, учитывая:

T data[N];

, где T - это тип, а N - это число, вы не можете сказать:

data = ...;

Учитывая вышеизложенное, и data++; пытается присвоить data, вы получаете ошибку.

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

Контекст объекта - это когда вы берете размер массива, используя sizeof, или когда вы берете его адрес (&data), или во время инициализации массива. Во всех других контекстах вы находитесь в ценностном контексте. Это включает передачу массива в функцию.

Итак, ваша функция:

void working_on_pointers ( int *pExpect, int aExpect[] ) {

эквивалентно

void working_on_pointers ( int *pExpect, int *aExpect ) {

Функция не может определить, был ли передан массив или указатель, поскольку все, что она видит, - это указатель.

Более подробная информация содержится в ответах на следующие вопросы:

Также см. Эту часть C для умных веб-сайта, который очень хорошо написан.

1 голос
/ 05 марта 2010

Попытка увеличить farr или varr не удалась, поскольку ни один из них не является указателем. Каждый массив. Имя массива, когда оно вычисляется само по себе (за исключением операнда оператора sizeof или address-of), оценивается как значение (значение r), которое имеет правильный тип, назначаемый указателю. Попытка увеличить это немного похоже на попытку увеличить 17. Вы можете увеличить значение int, которое содержит значение 17, но увеличение 17 само по себе не будет работать. Имя массива очень похоже на это.

Что касается вашей второй части, она довольно проста: если вы пытаетесь объявить параметр функции типа массива, компилятор молча «настраивает» его на тип указателя. Таким образом, в ваших working_on_pointers, aExpect и pExpect есть точно одного типа. Несмотря на нотацию в стиле массива, вы определили aExpect как имеющий тип указатель на int. Поскольку они одинаковы, вполне ожидаемо, что они будут действовать одинаково.

0 голосов
/ 05 марта 2010

хорошо, я могу ошибаться. но массивы и указатели могут использоваться попеременно.

int * ptr = (int *)malloc(2* sizeof(int));
ptr[0]=1;
ptr[1]=2;

printf ("%d\n", ptr[0]);
printf ("%d\n", ptr[1]);

здесь я объявил указатель и теперь обрабатываю его как массив.

кроме того:

Как следствие этого определения, нет очевидной разницы в поведение «подписки массива» оператор [] в применении к массивам и указатели. В выражении сформируйте [i], ссылку на массив "a" распадается на указатель, следуя правило выше, а затем подписывается так же, как будет переменная указателя в выражение р [я] (хотя возможный доступ к памяти будет разные, как объяснено в вопросе 2.2). В любом случае выражение x [i] (где x - это массив или указатель) по определению идентичен в * ((x) + (i)).

ссылка: http://www.lysator.liu.se/c/c-faq/c-2.html

0 голосов
/ 05 марта 2010

вам нужно понять основную концепцию массивов.

когда вы объявляете массив, т.е.

int farr[]

вы фактически объявляете указатель с помощью этого объявления

const int * farr

т.е.; «постоянный» указатель на целое число. поэтому, когда вы делаете farr ++, вы на самом деле пытаетесь сложить указатель, который является константой, поэтому компиляторы выдают ошибку.

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

P.S: Некоторое время я молчал, поэтому я не уверен в точном синтаксисе. но нижняя строка - это разница между указателем и постоянным указателем.

0 голосов
/ 05 марта 2010

Посмотрите на этот ответ, который я написал в отношении различий между указателями и массивами здесь на SO .

Надеюсь, это поможет.

...