Является ли имя массива указателем? - PullRequest
182 голосов
/ 29 октября 2009

Является ли имя массива указателем в C? Если нет, в чем разница между именем массива и переменной-указателем?

Ответы [ 9 ]

227 голосов
/ 29 октября 2009

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

Вот массив:

int a[7];

a содержит пробел для семи целых чисел, и вы можете поместить значение в одно из них с присваиванием, например так:

a[3] = 9;

Вот указатель:

int *p;

p не содержит пробелов для целых чисел, но может указывать на пробел для целых чисел. Мы можем, например, установить его так, чтобы он указывал на одно из мест в массиве a, например на первое:

p = &a[0];

Что может сбить с толку, так это то, что вы также можете написать это:

p = a;

Это не копирует содержимое массива a в указатель p (что бы это ни значило). Вместо этого имя массива a преобразуется в указатель на его первый элемент. Так что это назначение делает то же самое, что и предыдущее.

Теперь вы можете использовать p аналогично массиву:

p[3] = 17;

Причина, по которой это работает, заключается в том, что оператор разыменования массива в C, [ ], определяется в терминах указателей. x[y] означает: начать с указателя x, шаг y элементов вперед после того, на что указывает указатель, и затем взять все, что там есть. Используя арифметический синтаксис указателя, x[y] также можно записать как *(x+y).

Чтобы это работало с обычным массивом, таким как наш a, имя a в a[3] должно быть сначала преобразовано в указатель (в первый элемент в a). Затем мы продвигаемся на 3 элемента вперед и берем все, что есть. Другими словами: возьмите элемент в позицию 3 в массиве. (Который является четвертым элементом в массиве, поскольку первый из них пронумерован 0.)

Итак, в общем, имена массивов в C-программе (в большинстве случаев) преобразуются в указатели. Единственное исключение - когда мы используем оператор sizeof в массиве. Если бы a был преобразован в указатель в этом контексте, sizeof a дал бы размер указателя, а не фактического массива, что было бы довольно бесполезно, поэтому в этом случае a означает сам массив. *

31 голосов
/ 29 октября 2009

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

int arr[7];

/* arr used as value */
foo(arr);
int x = *(arr + 1); /* same as arr[1] */

/* arr not used as value */
size_t bytes = sizeof arr;
void *q = &arr; /* void pointers are compatible with pointers to any object */
14 голосов
/ 29 октября 2009

Если выражение типа массива (например, имя массива) появляется в большем выражении и не является операндом операторов & или sizeof, то тип выражения массива преобразуется из «N-элемент массива из T» в «указатель на T», а значением выражения является адрес первого элемента в массиве.

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

Редактировать

Отвечая на вопрос в комментарии:

Если я использую sizeof, могу ли я считать размер только элементов массива? Тогда массив «head» также занимает место с информацией о длине и указателем (а это значит, что он занимает больше места, чем обычный указатель)?

Когда вы создаете массив, единственным местом, которое выделяется, является пространство для самих элементов; не хранится хранилище для отдельного указателя или метаданных. Учитывая

char a[10];

что вы получаете в памяти

   +---+
a: |   | a[0]
   +---+ 
   |   | a[1]
   +---+
   |   | a[2]
   +---+
    ...
   +---+
   |   | a[9]
   +---+

Выражение a относится ко всему массиву, но объект a не отделен от самих элементов массива. Таким образом, sizeof a дает вам размер (в байтах) всего массива. Выражение &a дает вам адрес массива, , который совпадает с адресом первого элемента . Разница между &a и &a[0] заключается в типе результата 1 - char (*)[10] в первом случае и char * во втором.

Когда все становится странным, когда вы хотите получить доступ к отдельным элементам - выражение a[i] определяется как результат *(a + i) - с учетом значения адреса a, смещение i элементов ( не байт) ) с этого адреса и разыменуйте результат.

Проблема в том, что a - это не указатель или адрес, а весь объект массива. Таким образом, правило в C гласит, что всякий раз, когда компилятор видит выражение типа массива (например, a, имеющее тип char [10]) и , это выражение не является операндом sizeof или унарные операторы &, тип этого выражения преобразуется («распадается») в тип указателя (char *), а значением выражения является адрес первого элемента массива. Поэтому выражение a имеет тот же тип и значение, что и выражение &a[0] (и, соответственно, выражение *a имеет тот же тип и значение, что и выражение a[0]).

C был получен из более раннего языка, называемого B, и в B a был отдельным объектом указателя из элементов массива a[0], a[1] и т. Д. Ричи хотел сохранить массив B семантика, но он не хотел возиться с хранением отдельного объекта указателя. Таким образом, он избавился от этого. Вместо этого компилятор будет преобразовывать выражения массива в выражения указателя во время перевода по мере необходимости.

Помните, что я сказал, что в массивах не хранятся метаданные об их размере. Как только это выражение массива «распадается» на указатель, все, что у вас есть, - это указатель на один элемент. Этот элемент может быть первым из последовательности элементов или может быть отдельным объектом. Там нет никакого способа узнать, основываясь на самом указателе.

Когда вы передаете выражение массива в функцию, все, что получает функция, это указатель на первый элемент - он не знает, насколько большой массив (именно поэтому функция gets была такой угрозой и в конечном итоге была удален из библиотеки). Чтобы функция знала, сколько элементов в массиве, вы должны либо использовать значение часового (например, терминатор 0 в строках C), либо вы должны передать количество элементов в качестве отдельного параметра.


  1. Что * может * влиять на интерпретацию значения адреса - зависит от аппарата.
5 голосов
/ 29 октября 2009

Массив, объявленный так

int a[10];

выделяет память на 10 int с. Вы не можете изменить a, но вы можете сделать арифметику указателей с помощью a.

Указатель, подобный этому, выделяет память только для указателя p:

int *p;

Он не выделяет никаких int с. Вы можете изменить его:

p = a;

и использовать индексы массива как можно с:

p[2] = 5;
a[2] = 5;    // same
*(p+2) = 5;  // same effect
*(a+2) = 5;  // same effect
4 голосов
/ 29 октября 2009

Имя массива само по себе дает место в памяти, поэтому вы можете трактовать имя массива как указатель:

int a[7];

a[0] = 1976;
a[1] = 1984;

printf("memory location of a: %p", a);

printf("value at memory location %p is %d", a, *a);

И другие полезные вещи, которые вы можете сделать для указателя (например, добавление / вычитание смещения), вы также можете сделать с массивом:

printf("value at memory location %p is %d", a + 1, *(a + 1));

По языку, если C не представлял массив как просто своего рода «указатель» (педантично, это просто ячейка памяти. Он не может указывать на произвольную ячейку в памяти и не может управляться программистом). Нам всегда нужно кодировать это:

printf("value at memory location %p is %d", &a[1], a[1]);
1 голос
/ 08 ноября 2015

Я думаю, что этот пример проливает свет на проблему:

#include <stdio.h>
int main()
{
        int a[3] = {9, 10, 11};
        int **b = &a;

        printf("a == &a: %d\n", a == b);
        return 0;
}

Он прекрасно компилируется (с 2 предупреждениями) в gcc 4.9.2 и печатает следующее:

a == &a: 1

упс: -)

Итак, вывод - нет, массив не является указателем, он не хранится в памяти (даже не только для чтения) в качестве указателя, даже если он выглядит так, как вы можете, так как вы можете получить его адрес с помощью Оператор. Но, к сожалению, этот оператор не работает :-)), в любом случае, вы были предупреждены:

p.c: In function ‘main’:
pp.c:6:12: warning: initialization from incompatible pointer type
  int **b = &a;
            ^
p.c:8:28: warning: comparison of distinct pointer types lacks a cast
  printf("a == &a: %d\n", a == b);

C ++ отклоняет любые такие попытки с ошибками во время компиляции.

Edit:

Вот что я хотел продемонстрировать:

#include <stdio.h>
int main()
{
    int a[3] = {9, 10, 11};
    void *c = a;

    void *b = &a;
    void *d = &c;

    printf("a == &a: %d\n", a == b);
    printf("c == &c: %d\n", c == d);
    return 0;
}

Хотя c и a "указывают" на одну и ту же память, вы можете получить адрес указателя c, но вы не можете получить адрес указателя a.

0 голосов
/ 28 сентября 2015

Имя массива - это адрес 1-го элемента массива. Так что да, имя массива является константным указателем.

0 голосов
/ 01 декабря 2014

Имя массива ведет себя как указатель и указывает на первый элемент массива. Пример:

int a[]={1,2,3};
printf("%p\n",a);     //result is similar to 0x7fff6fe40bc0
printf("%p\n",&a[0]); //result is similar to 0x7fff6fe40bc0

Оба оператора печати будут выдавать один и тот же вывод для машины. В моей системе это дало:

0x7fff6fe40bc0
0 голосов
/ 29 октября 2009

Массив - это набор последовательных и смежных элементов в памяти. В C имя массива является индексом первого элемента, и применяя смещение, вы можете получить доступ к остальным элементам. «Индекс к первому элементу» действительно является указателем на направление памяти.

Разница с переменными-указателями в том, что вы не можете изменить местоположение, на которое указывает имя массива, поэтому похоже на указатель const (похоже, не то же самое. См. Комментарий Марка) Но также то, что вам не нужно разыменовывать имя массива, чтобы получить значение, если вы используете арифметику указателей:

char array = "hello wordl";
char* ptr = array;

char c = array[2]; //array[2] holds the character 'l'
char *c1 = ptr[2]; //ptr[2] holds a memory direction that holds the character 'l'

Так что ответ вроде "да".

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