2009-11-23 5 views
7

Учитывая следующий код:Сходства и различия между массивами и указателями через практический пример

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

int main() 
{ 
    int a[1]; 
    int * b = malloc(sizeof(int)); 

    /* 1 */ 
    scanf("%d", &a); 
    printf("%d\n", a[0]); 

    /* 2 */ 
    scanf("%d", &b); 
    printf("%d\n", b[0]); 

    return 0; 
} 

следующие предупреждения получены при компиляции (i686-яблоко-darwin9-GCC-4.0.1):

array.c: In function 'main': 
array.c:9: warning: format '%d' expects type 'int *', but argument 2 has type 'int (*)[0u]' 
array.c:14: warning: format '%d' expects type 'int *', but argument 2 has type 'int **' 

но, почему казнь ошибки происходят во втором PRINTF, тем временем он работает в первом PRINTF?

Более того, почему он получает тот же результат, если первый scanf заменен scanf ("% d", a);?

Спасибо очень много заранее

ответ

10

В большинстве контекстов выражение типа массива будет неявно преобразовано из «массива N-элементов от T» в «указатель на T», и его значение будет установлено на первый элемент массива. Исключения из этого правила - это когда массив является операндом операторов & или sizeof или массив является строковым литералом, который используется для инициализации другого массива в объявлении.

Так как же все это относится к вашему коду?

В строке

scanf("%d", &a); 

Вы подаете оператор & в массив. Это подавляет неявное преобразование из «массива Т» в «указатель на Т» и возвращает значение типа «указатель на массив из Т» или T (*)[N] (отсюда и первое предупреждение). Теперь выясняется, что значение указателя на массив и значение указателя на первый элемент массива одинаковы, у них просто разные типы.Так если предположить, что a находится по адресу 0x0001000:

expression  type   value   note 
----------  ----   -----   ---- 
     a  int *   0x0001000  implicitly converted to pointer 
     &a  int (*)[N] 0x0001000  
    &a[0]  int *   0x0001000 

Вот почему ваш первый вызов scanf() «работает»; вы передаете правильный указатель значение, но компилятор жалуется, потому что типа этого выражения не соответствует ожидаемой функции. Если бы вы написали

scanf("%d", a); 

вы бы не получили каких-либо предупреждений, поскольку тип a будет принято быть int *, что это то, что scanf() ожидает. Обратите внимание, что это идентично вызову

scanf("%d", &a[0]); 

Как для b ...

Вы явно объявить b как указатель на Int и назначить блок памяти к нему. Когда вы применяете к нему оператора &, то, что вы получаете, является адресом переменной b с типом int ** (отсюда второе предупреждение), а не адресом, на который указывает b.

expression  type   value   note 
----------  ----   -----   ---- 
     b  int *   0x0040000  value contained in b 
     &b  int **  0x0001000  address of b 

Для этого случая, вы просто передать неукрашенный b:

scanf("%d", b); 
5

Массив помещается в стек, адрес для первого элемента является такой же, как адрес. & а [0] и & а является тот же адрес

Массив б выделяется таНос, адрес для хранения в куче, в то время как адрес для указателя б находится в стеке. & b [0] не совпадает с адресом & b.

Вот почему первый scanf и printf работают, но не второй.

C-FAQ объясняет это более подробно.

2

В первом scanf вы передаете ссылку на массив. В массивах C указываются указатели на блок памяти выделенного типа, в вашем случае int * и выражение, подобное a[0], преобразуется в *(a + 0) (что приводит к появлению смешного варианта 0[a], который фактически скомпилируется.) Этот массив выделяется в стеке , Второй массив выделяется в куче, а стек содержит переменную указателя для этого массива.

В обоих случаях вы не передаете указатель на первую запись массива, а на массив и указатель на массив соответственно.

Ваш первый scanf перезаписывает то, что является массивом, так как он выделен в стеке, ваше значение заканчивается (удачей) в массиве.

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

1

Это нормально ...

Во-первых, зсапЕ требует указатель. «a» и «b» уже являются указателями! So:

/* 1 */ 
scanf("%d", a); 
printf("%d\n", a[0]); 

/* 2 */ 
scanf("%d", b); 
printf("%d\n", b[0]); 

Будет работать.

Обычно/* 1 */не должен работать. Но gcc преобразует «& a» на «a», потому что «& a» не имеет никакого смысла.

printf("&a = %p\n", &a); 
printf("a = %p\n", a); 
printf("&b = %p\n", &b); 
printf("b = %p\n", b); 

&a = 0x7ffff6be67d0 
a = 0x7ffff6be67d0 
&b = 0x7ffff6be67c8 
b = 0xb0b010 

Вы не можете использовать адрес. Но b является «нормальной переменной» указателя типа, и таким образом вы можете принять его адрес «& b».

Вкл/* 2 */вы вводите значение, введенное пользователем в b, и, таким образом, * b (или b [0]) выйдет из строя, если пользователь не введет допустимый читаемый адрес памяти.

1

В вашем случае происходит то, что вы передаете обе переменные a и b оператору & функции scanf. То, что делает этот оператор, - «спросить» адрес памяти переменной и передать этот адрес функции scanf. Но, поскольку обе ваши переменные являются указателями, то, что у них действительно есть адрес памяти, поэтому, когда вы проходите & a или & b, вы передаете память указателю, а не адресу памяти, который он удерживает.

Пример:

int x; 
int *ptr; 

x = 10; 

предположим, что адрес памяти х 1000. Вы сохраняете номер 10 по адресу памяти 1000. Теперь вы делаете это:

ptr = &x; 

Вы храните адрес 1000 в указателе. Но 1000, будучи адресом, является самим номером, поэтому указателю, как и x, по-прежнему нужен адрес памяти для хранения этой информации. Предположу, местоположение указателя памяти в 1004. Теперь рассмотрит пример:

*ptr == 10; //x content 
ptr == 1000 //x memory address 
&ptr == 1004 // ptr memory address. 

Так что, если вы хотите передать Scanf переменных х, но с помощью указателя, вы должны передать й адрес, хранящийся в нем

scanf("%d", ptr); 

Просто ilustrate другого примера указателей и векторов

int main 
{ 
    int vet[5]; 
    int *ptr; 

    ptr = vet; 

    for(int i = 0; i < 5; ++i) 
    { 
     scanf("%d", (ptr+i)); 
    } 
} 

Здесь вы можете прочитать вектор, используя указатель. Кроме того, используя арифметику указателя, вы можете перебирать адреса памяти в векторе.

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