Является ли имя массива указателем?
Является ли имя массива указателем в C? Если нет, то в чем разница между именем массива и переменной-указателем?
13 ответов
Массив - это массив, а указатель - это указатель, но в большинстве случаев имена массивов преобразуются в указатели. Термин часто используется, что они распадаются на указатели.
Вот массив:
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
означает сам массив.
Когда в качестве значения используется массив, его имя представляет адрес первого элемента.
Когда массив не используется в качестве значения, его имя представляет весь массив.
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 - int (*)[10]
в первом случае и int *
во-вторых.
То, что становится странным, это когда вы хотите получить доступ к отдельным элементам - выражению 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), либо вы должны передать число элементов в качестве отдельного параметра.
- Какая * может * повлиять на интерпретацию значения адреса - зависит от машины.
Массив объявлен так
int a[10];
выделяет память на 10 int
s. Вы не можете изменить 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 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]);
В следующем примере показано конкретное различие между именем массива и указателем. Допустим, вы хотите представить 1D-линию с некоторым заданным максимальным размером, вы можете сделать это либо с помощью массива, либо с помощью указателя:
typedef struct {
int length;
int line_as_array[1000];
int* line_as_pointer;
} Line;
Теперь посмотрим на поведение следующего кода:
void do_something_with_line(Line line) {
line.line_as_pointer[0] = 0;
line.line_as_array[0] = 0;
}
void main() {
Line my_line;
my_line.length = 20;
my_line.line_as_pointer = (int*) calloc(my_line.length, sizeof(int));
my_line.line_as_pointer[0] = 10;
my_line.line_as_array[0] = 10;
do_something_with_line(my_line);
printf("%d %d\n", my_line.line_as_pointer[0], my_line.line_as_array[0]);
};
Этот код выведет:
0 10
Это потому, что при вызове функции do_something_with_line
объект был скопирован так:
- Указатель
line_as_pointer
по-прежнему содержит тот же адрес, на который указывал - Массив
line_as_array
был скопирован на новый адрес, не превышающий объем функции
Таким образом, в то время как массивы не задаются значениями, когда вы напрямую вводите их в функции, когда вы инкапсулируете их в структуры, они задаются по значению (т. Е. Копируются), что подчеркивает здесь основное отличие в поведении по сравнению с реализацией с использованием указателей.
Я думаю, что этот пример проливает свет на проблему:
#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
указатель.
НЕТ. Имя массива НЕ является указателем. Вы не можете назначать или изменять имя массива, но можете для указателя.
int arr[5];
int *ptr;
/* CAN assign or increment ptr */
ptr = arr;
ptr++;
/* CANNOT assign or increment arr */
arr = ptr;
arr++;
/* These assignments are also illegal */
arr = anotherarray;
arr = 0;
Из книги К&Р:
Существует одно различие между именем массива и указателем, о котором следует помнить. Указатель — это переменная, но имя массива — не переменная.
sizeof - еще одна большая разница.
sizeof(arr); /* size of the entire array */
sizeof(ptr); /* size of the memory address */
В некоторых ситуациях массивы ведут себя как указатели или распадаются на них (&arr[0]
). Вы можете увидеть другие ответы для большего количества примеров этого. Повторим некоторые из этих случаев:
void func(int *arr) { }
void func2(int arr[]) { } /* same as func */
ptr = arr + 1; /* pointer arithmetic */
func(arr); /* passing to function */
Несмотря на то, что вы не можете назначать или изменять имя массива, вы, конечно, можете изменить содержимое массива.
arr[0] = 1;
Вот как я понял различия (и сходства) между массивами и указателями.
int v = 0;
| |
|-------|
100 | | Name 1 - v
| 0 | Name 2 - *100
| |
103 | |
|-------|
| |
Even though *100 is technically not a name, and is instead a unary operator
(i.e. *) along with its operand (i.e. 100), we can still think of it as a name
in order to understand how addresses/pointers work.
Now, &v means the starting address of the variable whose name is v (i.e. 100).
Also, the data type of 100 is int* (i.e. pointer-to-int).
1. printf("%d\n", v)
is equivalent to printf("%d\n", 0)
is equivalent to printf("%d\n", *100)
2. v = 10
is equivalent to *100 = 10
3. v + 1
is equivalent to 0 + 1
is equivalent to *100 + 1
4. scanf("%d", &v)
is equivalent to scanf("%d", &(*100))
is equivalent to scanf("%d", 100)
5. func(&v)
is equivalent to func(&(*100))
is equivalent to func(100)
and so on.
Since the data type of 10 is int, therefore it can be assigned to the variable
whose name is v because that variable's data type is also int.
Similarly, since the data type of 100 is int* (i.e. pointer-to-int), therefore
it can be assigned to a variable whose data type is also int*.
////////////////////////////////////////////////////////////////////////////////
int* ptr = &v; is equivalent to int* ptr = 100;
| |
|-------|
100 | | Name 1 - v
| 0 | Name 2 - *100
| |
103 | |
|-------|
| |
| |
|-------|
200 | | Name 1 - ptr
| | Name 2 - *200
| |
| 100 |
| |
| |
| |
207 | |
|-------|
| |
Now, for eg., when evaluating v + 1, v is equivalent to 0 because 0 is the value
which is stored in the variable whose name is v.
Similarly, when evaluating *ptr, ptr is equivalent to 100 because 100 is the
value which is stored in the variable whose name is ptr.
1. printf("%d\n", v)
is equivalent to printf("%d\n", 0)
is equivalent to printf("%d\n", *100)
is equivalent to printf("%d\n", *ptr)
2. v = 10
is equivalent to *100 = 10
is equivalent to *ptr = 10
3. v + 1
is equivlent to 0 + 1
is equivalent to *100 + 1
is equivalent to *ptr + 1
4. scanf("%d", &v)
is equivalent to scanf("%d", &(*100))
is equivalent to scanf("%d", 100)
is equivalent to scanf("%d", &(*ptr))
is equivalent to scanf("%d", ptr)
5. func(&v)
is equivalent to func(&(*100))
is equivalent to func(100)
is equivalent to func(&(*ptr))
is equivalent to func(ptr)
and so on.
So, we can also think that the variable whose name is v gets a third name, i.e.
*ptr.
| |
|-------|
100 | | Name 1 - v
| 0 | Name 2 - *100
| | Name 3 - *ptr
103 | |
|-------|
| |
Also, to get the address of this variable, we can use &v, 100 or ptr.
////////////////////////////////////////////////////////////////////////////////
int main(void)
{
int v;
...
func(&v);
...
}
void func(int* ptr)
{
...
*ptr = 5;
...
}
Here, the function call func(&v) is equivalent to func(100).
So, when func() is executed, the name v of the variable which is local to main()
goes out of scope.
| |
|-------|
100 | | Name 1 - xxxx
| 0 | Name 2 - *100
| |
103 | |
|-------|
| |
Now, a variable which is local to func() whose name is ptr and whose data type
is pointer-to-int is created.
| |
|-------|
200 | | Name 1 - ptr
| | Name 2 - *200
| |
| 100 |
| |
| |
| |
207 | |
|-------|
| |
Now, the variable which is local to main() gets a new name.
| |
|-------|
100 | | Name 1 - xxxx
| 0 | Name 2 - *100
| | Name 3 - *ptr
103 | |
|-------|
| |
And, to get the address of the variable local to main(), we can use 100 or ptr.
After executing *ptr = 5, when func() returns, the variable which is local to
func() and whose name is ptr is destroyed, and the name v of the variable which
is local to main() is restored.
Also, since the variable which is local to func() and whose name is ptr is
destroyed, therefore the name *ptr of the variable which is local to main() is
also destroyed.
| |
|-------|
100 | | Name 1 - v
| 5 | Name 2 - *100
| |
103 | |
|-------|
| |
////////////////////////////////////////////////////////////////////////////////
int arr[3] = {7, 8, 9};
arr is the name of the entire array.
| |
|-------|
300 | | Name - *300
| 7 |
| |
303 | |
|-------|
304 | | Name - *304
| 8 |
| |
307 | |
|-------|
308 | | Name - *308
| 9 |
| |
311 | |
|-------|
| |
When the expression v + 1.0 is evaluated, the data type of v is converted from
int to double only for the purpose of evaluating that expression.
Similarly, except for those 4 cases (&, sizeof, alignof, string literal used to
initialize an array), arr is converted to the address of the first element of
the array only for the purpose of evaluating the corresponding expression.
Also, for eg., if the array consists of elements of data type int, then the
data type of the resultant address is pointer-to-int. So, the resultant address
can be assigned to a variable of data type pointer-to-int.
For eg., int* ptr = arr;
So, in other words, except for those 4 cases, arr is converted to a pointer to
the first element of the array only for the purpose of evaluating the
corresponding expression.
When an integer is added to an address/pointer, the resultant address is
calculated according to the data type of the address/pointer.
For eg., 300 + 1 gives 304.
arr[i] is equivalent to *(arr + i).
Now, arr[1]
is equivalent to *(arr + 1)
is equivalent to *(300 + 1)
is equivalent to *304
And, &(arr[1])
is equivalent to &(*(arr + 1))
is equivalent to &(*(300 + 1))
is equivalent to &(*304)
is equivalent to 304
Also, arr + 1
is equivalent to 300 + 1
is equivalent to 304
1. printf("%d\n", arr[1])
is equivalent to printf("%d\n", 8)
is equivalent to printf("%d\n", *(arr + 1))
is equivalent to printf("%d\n", *304)
2. arr[1] = 10
is equivalent to *(arr + 1) = 10
is equivalent to *304 = 10
3. arr[1] + 1
is equivalent to 8 + 1
is equivalent to *(arr + 1) + 1
is equivalent to *304 + 1
4. scanf("%d", &(arr[1]))
is equivalent to scanf("%d", &(*(arr + 1)))
is equivalent to scanf("%d", &(*304))
is equivalent to scanf("%d", 304)
is equivalent to scanf("%d", arr + 1)
5. func(&(arr[1]))
is equivalent to func(&(*(arr + 1)))
is equivalent to func(&(*304))
is equivalent to func(304)
is equivalent to func(arr + 1)
and so on.
So, we can also think that the elements of the array have other names.
| |
|-------|
300 | | Name 1 - *300
| 7 | Name 2 - arr[0]
| | Name 3 - *(arr + 0)
303 | |
|-------|
304 | | Name 1 - *304
| 8 | Name 2 - arr[1]
| | Name 3 - *(arr + 1)
307 | |
|-------|
308 | | Name 1 - *308
| 9 | Name 2 - arr[2]
| | Name 3 - *(arr + 2)
311 | |
|-------|
| |
Also, to get the address of, for eg., arr[1], we can use &(arr[1]), 304 or
arr + 1.
////////////////////////////////////////////////////////////////////////////////
int main(void)
{
int arr[3];
...
func(arr);
...
}
void func(int* arr)
{
...
arr[1] = 6;
...
}
Here, the function call func(arr) is equivalent to func(300).
So, when func() is executed, the name arr of the entire array which is local to
main() goes out of scope, which means that Name 2 and Name 3 of every element of
the array also go out of scope.
| |
|-------|
300 | | Name 1 - *300
| 7 | Name 2 - xxxx
| | Name 3 - xxxx
303 | |
|-------|
304 | | Name 1 - *304
| 8 | Name 2 - xxxx
| | Name 3 - xxxx
307 | |
|-------|
308 | | Name 1 - *308
| 9 | Name 2 - xxxx
| | Name 3 - xxxx
311 | |
|-------|
| |
Now, a variable which is local to func() whose name is arr and whose data type
is pointer-to-int is created.
| |
|-------|
400 | | Name 1 - arr
| | Name 2 - *400
| |
| 300 |
| |
| |
| |
407 | |
|-------|
| |
It should be noted that when the expression arr[1] = 6 is evaluated inside
func(), arr isn't the name of the array which is local to main(), and is instead
the name of the variable which is local to func().
So, there is no need to convert arr to a pointer, as arr is already a pointer.
Now, similar to main(), inside func() also
arr[1]
is equivalent to *(arr + 1)
is equivalent to *(300 + 1)
is equivalent to *304
And, &(arr[1])
is equivalent to &(*(arr + 1))
is equivalent to &(*(300 + 1))
is equivalent to &(*304)
is equivalent to 304
Also, arr + 1
is equivalent to 300 + 1
is equivalent to 304
So, the elements of the array which is local to main() get new names.
| |
|-------|
300 | | Name 1 - *300
| | Name 2 - xxxx
| 7 | Name 3 - xxxx
| | Name 4 - arr[0]
303 | | Name 5 - *(arr + 0)
|-------|
304 | | Name 1 - *304
| | Name 2 - xxxx
| 8 | Name 3 - xxxx
| | Name 4 - arr[1]
307 | | Name 5 - *(arr + 1)
|-------|
308 | | Name 1 - *308
| | Name 2 - xxxx
| 9 | Name 3 - xxxx
| | Name 4 - arr[2]
311 | | Name 5 - *(arr + 2)
|-------|
| |
And, to get the address of, for eg., arr[1], we can use &(arr[1]), 304 or
arr + 1.
After executing arr[1] = 6, when func() returns, the variable which is local to
func() and whose name is arr is destroyed, and the name arr of the entire array
which is local to main() is restored, which means that Name 2 and Name 3 of
every element of the array are also restored.
Also, since the variable which is local to func() and whose name is arr is
destroyed, therefore Name 4 and Name 5 of every element of the array are also
destroyed.
| |
|-------|
300 | | Name 1 - *300
| 7 | Name 2 - arr[0]
| | Name 3 - *(arr + 0)
303 | |
|-------|
304 | | Name 1 - *304
| 6 | Name 2 - arr[1]
| | Name 3 - *(arr + 1)
307 | |
|-------|
308 | | Name 1 - *308
| 9 | Name 2 - arr[2]
| | Name 3 - *(arr + 2)
311 | |
|-------|
| |
Имя массива ведет себя как указатель и указывает на первый элемент массива. Пример:
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-го элемента массива. Так что да, имя массива является константным указателем.
Прежде всего, по K&R, ответ - да.
На концептуальном уровне имя массива - это ptr, они абсолютно одно и то же, КОНЦЕПТУАЛЬНО.
Как компилятор реализует массив, зависит от компилятора. Это аргумент между теми, кто говорит, что они не одно и то же, и теми, кто говорит, что они есть.
Компилятор может реализовать int a[5]
как указатель a
на неназванный кусок памяти, который может содержать 5 целых чисел. Но они этого не делают. Разработчику компилятора легче сгенерировать код для простого массива, а затем поменять указатель на a
,
Во всяком случае, концептуально, область данных, выделенная int a[5];
на утверждение может ссылаться ptr a
или на него может ссылаться указатель, созданный &a[0]
заявление.