Вопрос об указателе массива C - PullRequest
2 голосов
/ 11 июля 2009

Я думал, что переменная массива не может быть изменена в C, т.е. адрес массива неизменяем, но следующий код противоречит мое предположение:

#include <stdlib.h>
#include <stdio.h>

void changeArray(int **a)
{
   *a = malloc(sizeof(int));  
}

int main()
{
   int a[10];
   a[0] = 1;
   printf("%d\n",a[0]);
   changeArray(&a);
   printf("%d\n",a[0]);
}

Этот код печатает:

1
6750576(some random value)

Очевидно, что базовый адрес массива был изменен. Как это возможно?

Ответы [ 6 ]

14 голосов
/ 11 июля 2009

Код играет в игры зло указатель, без удачи. Вы передаете указатель типа int(*)[10] функции, которая хочет указатель типа int**. Указатель, который вы передаете, имеет в качестве значения «базовый адрес» массива.

Таким образом, когда вы разыменовываете int**, он думает, что по этому адресу он получает int*, хотя то, на что он смотрит, является int объектом. И он записывает адрес, возвращенный с malloc в эту ячейку памяти.

Вернувшись в главное, вы распечатываете эту ячейку. Обратите внимание, что это значение первого элемента массива, а не адрес первого элемента массива. Таким образом, выводится целочисленная интерпретация адреса, который вы написали в вызываемой функции.

То, что вы делаете, это неопределенное поведение: функция хочет int**, поэтому вы должны дать ей int**,


Вот как я думаю вы рассматриваете вопрос

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

Но это представление ошибочно. Первая точка наиболее ошибочна, поскольку имя массива не является постоянным указателем. Если бы первый пункт был верным, ваш фрагмент имел бы намного больше смысла, на самом деле. Но имя массива будет генерировать значение адреса, которое ссылается на его первый элемент, когда вы используете его в выражении, за исключением очень немногих случаев (sizeof, address-of).

Поскольку это значение адреса не генерируется при использовании address-of, вместо этого вы получите указатель на этот массив (именно то, что вы написали с помощью оператора address-of). Поскольку имеет смысл, что массив и первый его элемент имеют одинаковый адрес, адрес массива совпадает с адресом его первого элемента. Так что вы на самом деле сделали запись в первый элемент, вместо записи в какой-то указатель (которого на самом деле нет).


Ответ на комментарий

Рассмотрим, что происходит, когда тип массива имеет значение на практике (из этих экспериментов). У вас есть массив, элементы которого сами являются массивами.

// array of 3 "array of 10 int"
int a[3][10];

Таким образом, используя a в выражении, отличном от & и sizeof, вы получите указатель на первый элемент, int(*)[10]. Это очень важно, потому что следующее записывает в целое число смещение 2*sizeof(int)*10 байт

a[2][0] = 1;
  // (a + 2) refers to a offset by 2*sizeof(int)*10 bytes

Если a в этом выражении даст вам int**, то компилятор не будет знать, где он должен правильно хранить целое число 1, потому что любая информация о размере о типе элемента теряется. Конечно, он может хранить размер где-то в памяти, но где? В массиве нет места для этого. И, кроме того, sizeof больше не сможет дать вам результат компиляции.

2 голосов
/ 11 июля 2009

В этом коде вы не изменили базовый адрес массива. Вы изменили значение первого элемента в массиве.

1 голос
/ 11 июля 2009

&a не приводит к int **, как вы думаете. Он оценивается как int *[10] так же, как a в следующем фрагменте кода:

#include <stdio.h>

int main(void)
{
        int a[10];
        printf("%p %p\n", a, &a);
        return 0;
}

Это печатает для меня:

0xbffff058 0xbffff058

Таким образом, ваша функция changearray назначает указатель, возвращаемый вашим malloc первому элементу массива a в main.

Далее, если вы скомпилировали с -Wall, вы увидите предупреждение (как следует):

bad.c:14: warning: passing argument 1 of ‘changeArray’ from incompatible pointer type
0 голосов
/ 31 октября 2012
void changeArray(int **a)
{
    printf("\n *a=%p",*a);
    *a = malloc(sizeof(int));
    printf("\n *a=%p",*a);
}


int main(){

   int a[10];
   a[0] = 1;
   printf("\n..correct...Array Address=%p",a);
   printf("\nbefore.....%d",a[0]);
   changeArray(&a);
   printf("\nafter....%d",a[0]);

   printf("\n Array address=%p",a);
return 0;
}

Адрес массива = 0x7fff287e6130 До:

1
*a=0x1
*a=0x13a3e010

После:

329506832
Array address=0x7fff287e6130

329506832 - десятичное значение 0x13a3e010

Вы можете увидеть это в своем калькуляторе для Windows.

Адрес массива является константным указателем, поэтому он никогда не изменится.

Здесь * означает первое значение массива ... не адрес массива. Для перекрестной проверки ... после итерации функции ChangeArray по массиву вы узнаете, что это первый элемент, который изменяется, а не адрес массива, который является постоянным элементом ...

Вы используете системный вызов malloc, вы запрашиваете 4 байта памяти, и затем этот адрес сохраняется в первом элементе массива ....

Возьмите ручку и бумагу, тогда вы легко ее получите.

0 голосов
/ 11 июля 2009

Выражение &a возвращает адрес массива a. Этот адрес передается в функцию chageArray(), как если бы он был **int.

Внутри функции changeArray() выражение *a разыменяет указатель один раз, поэтому результатом выражения является lvalue, соответствующее переменной int по этому адресу.

За пределами функции changeArray () вы можете думать, что выражение &a совпадает с &a[0].

Итак, что вы сделали в функции changeArray ():

*(&a[0]) = malloc(sizeof(int));

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

a[0] = malloc(sizeof(int));
0 голосов
/ 11 июля 2009

Какой компилятор вы использовали для компиляции кода? Я пытался использовать VC ++ 2005, код недопустим, который говорит, что «не может преобразовать параметр 1 из« int (* __ w64) [10] »в« int ** »«

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

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