Является ли имя массива указателем в C? Если нет, в чем разница между именем массива и переменной-указателем?
&array[0]
указатель, а не массив;)
Является ли имя массива указателем в C? Если нет, в чем разница между именем массива и переменной-указателем?
&array[0]
указатель, а не массив;)
Ответы:
Массив - это массив, а указатель - это указатель, но в большинстве случаев имена массивов преобразуются в указатели. Термин часто используется, что они распадаются на указатели.
Вот массив:
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)
.
Чтобы это работало с обычным массивом, таким как our a
, имя a
в a[3]
должно быть сначала преобразовано в указатель (на первый элемент в a
). Затем мы продвигаемся на 3 элемента вперед и берем все, что есть. Другими словами: возьмите элемент в позицию 3 в массиве. (Который является четвертым элементом в массиве, поскольку первый из них пронумерован 0.)
Итак, в итоге, имена массивов в программе на C (в большинстве случаев) преобразуются в указатели. Единственное исключение - когда мы используем sizeof
оператор в массиве. Если бы он a
был преобразован в указатель в этом контексте, он sizeof a
бы дал размер указателя, а не фактического массива, что было бы довольно бесполезно, поэтому в этом случае a
подразумевается сам массив.
functionpointer()
и другое и (*functionpointer)()
, как ни странно, означают одно и то же.
sizeof()
, другой контекст, в котором нет спада массива-> указатель, является оператором &
- в приведенном выше примере &a
будет указатель на массив из 7 int
, а не указатель на единицу int
; то есть, его тип будет int(*)[7]
, который неявно конвертируется в int*
. Таким образом, функции могут на самом деле брать указатели на массивы определенного размера и применять ограничение через систему типов.
Когда в качестве значения используется массив, его имя представляет адрес первого элемента.
Когда массив не используется в качестве значения, его имя представляет весь массив.
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 */
Если выражение типа массива (например, имя массива) появляется в большем выражении и не является операндом ни оператора, &
ни 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), либо вы должны передать количество элементов в качестве отдельного параметра.
sizeof
является оператором, и он оценивает число байтов в операнде (или выражение, обозначающее объект, или имя типа в скобках). Таким образом, для массива sizeof
вычисляется количество элементов, умноженное на количество байтов в одном элементе. Если int
ширина 4 байта, то массив из 5 элементов int
занимает 20 байтов.
[ ]
тоже не особенный? Например, int a[2][3];
тогда for x = a[1][2];
, хотя он может быть переписан как x = *( *(a+1) + 2 );
, здесь a
не преобразуется в тип указателя int*
(хотя if a
является аргументом функции, в которую он должен быть преобразован int*
).
a
имеет тип int [2][3]
, который «распадается» для ввода int (*)[3]
. Выражение *(a + 1)
имеет тип int [3]
, который «распадается» на int *
. Таким образом, *(*(a + 1) + 2)
будет иметь тип int
. a
указывает на первый 3-элементный массив int
, a + 1
указывает на второй 3-элементный массив int
, *(a + 1)
является вторым 3-элементным массивом int
, *(a + 1) + 2
указывает на третий элемент второго массива int
, так же *(*(a + 1) + 2)
как и третий элемент второго массива int
, Как это отображается в машинном коде, полностью зависит от компилятора.
Массив объявлен так
int a[10];
выделяет память на 10 int
с. Вы не можете изменить, a
но вы можете сделать арифметику указателя с a
.
Такой указатель выделяет память только для указателя p
:
int *p;
Он не выделяет никаких int
s. Вы можете изменить это:
p = a;
и использовать индексы массива, как вы можете с:
p[2] = 5;
a[2] = 5; // same
*(p+2) = 5; // same effect
*(a+2) = 5; // same effect
int
секунд с автоматической продолжительностью хранения».
Имя массива само по себе дает место в памяти, поэтому вы можете рассматривать имя массива как указатель:
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]);
Я думаю, что этот пример проливает свет на проблему:
#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 ++ отказывается от любых таких попыток с ошибками во время компиляции.
Редактировать:
Вот что я хотел продемонстрировать:
#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
указателя.
-std=c11 -pedantic-errors
, вы получите ошибку компилятора для написания недопустимого кода C. Причина в том, что вы пытаетесь присвоить int (*)[3]
переменную типа int**
, которые являются двумя типами, которые не имеют абсолютно никакого отношения друг к другу. Так что этот пример должен доказать, я понятия не имею.
int **
Тип не точка там, следует лучше использовать void *
для этого.
Имя массива ведет себя как указатель и указывает на первый элемент массива. Пример:
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
Массив - это совокупность последовательных и смежных элементов в памяти. В C имя массива является индексом первого элемента, и применяя смещение, вы можете получить доступ к остальным элементам. «Индекс к первому элементу» действительно является указателем на направление памяти.
Разница с переменными-указателями в том, что вы не можете изменить местоположение, на которое указывает имя массива, поэтому оно похоже на константный указатель (это похоже, а не то же самое. См. Комментарий Марка). Но также то, что вам не нужно разыменовывать имя массива, чтобы получить значение, если вы используете арифметику указателей:
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'
Так что ответ вроде «да».
Имя массива - это адрес 1-го элемента массива. Так что да, имя массива является константным указателем.