Да, вы можете взять адрес за пределами конца массива, но вы не можете его разыменовать. Для вашего массива из 10 элементов подойдет массив + 10
. Несколько раз (в том числе комитетом) утверждалось, действительно ли & array [10]
вызывает неопределенное поведение или нет (и если да, то действительно ли должно). Суть в том, что, по крайней мере, в соответствии с текущими стандартами (как C, так и C ++) он официально вызывает неопределенное поведение, но если есть один компилятор, для которого он фактически не работает, никто ни по одному из аргументов не смог найти или процитировать его.
Править: На этот раз я наполовину правильно помнил - это был (часть) официального отчета о дефектах для комитета, и, по крайней мере, некоторые члены комитета (например, Том Плам) подумали, что формулировка была был изменен таким образом, чтобы не приводили к неопределенному поведению. OTOH, DR датируется 2000 годом, и статус по-прежнему «черновой», поэтому остается открытым вопрос, действительно ли он исправлен или когда-либо будет (я не просматривал N3090 / 3092, чтобы понять).
Однако в C99 это явно не неопределенное поведение.
Нет. Это не определено. массив [10]
составляет * (массив + 10)
. Другими словами, вы разыменовали недопустимый указатель.
массив [10]
эквивалентен * (массив + 10)
(а также эквивалентен 10 [массив]
), поэтому & array [10]
действует как удаление *
из * (array + 10)
. Любой достойный компилятор должен выдавать один и тот же код (и такое же предупреждение).
Как указывалось в других ответах, выражение array [10]
эквивалентно * (array + 10)
, что, по-видимому, приводит к неопределенное разыменование элемента сразу за концом массива. однако выражение & array [10]
эквивалентно & * (array + 10)
, а в стандарте C99 ясно сказано (6.5.3.2 Операторы адресации и косвенного обращения):
Унарный оператор & возвращает адрес своего операнда. Если операнд имеет тип «тип», результат будет иметь тип «указатель на тип». Если операнд является результатом унарного оператора
*
, ни этот оператор, ни оператор & не оцениваются, и результат такой же, как если бы оба были опущены, за исключением того, что ограничения на операторы все еще применяются, и результат не lvalue. Точно так же, если операнд является результатом оператора[]
, ни оператор&
, ни унарный*
, подразумеваемый[]
, не оцениваются, и результат будет следующим: если оператор&
был удален, а оператор[]
заменен на оператор +.
Итак, в и array [10]
нет ничего неопределенного - операции разыменования не происходит.
На самом деле ответить на этот вопрос довольно просто.
Все, что вы делаете, это математика указателя, в этом нет ничего недействительного. Если вы каким-то образом попытаетесь ИСПОЛЬЗОВАТЬ полученные указатели, это будет зависеть от того, имеет ли ваш процесс доступ к памяти, на которую указывает этот указатель, и если да, то какие у него разрешения?
foo.cc:
#include <iostream>
using namespace std;
int main() {
int array[10];
int *a = array + 10;
int *b = &array[10];
int *c = &array[3000];
cerr << "Pointers values are: " << a << " " << b << " " << c << endl;
return 0;
}
Для компиляции:
g++ -Werror -Wall foo.cc -o foo
не должно выдавать НИКАКИХ предупреждений, потому что int * b = & array [10] действителен
На самом деле так же и следующая строка, добавленная мной, чтобы просто указать на математику указателя.
foo не только скомпилируется, но и будет работать без сбоев:
./foo
Pointers are: 0x7fff1d356e68 0x7fff1d356e68 0x7fff1d359d20
В основном синтаксис массива foo [idx] является сокращенным и чистым представлением математики указателя.
Если есть некоторая путаница относительно c99, стандарт НЕ ВЛИЯЕТ на эту математику в любом случае и четко определен.
То же самое в C99:
#include <stdio.h>
int main() {
int array[10];
int *a = array + 10;
int *b = &array[10];
int *c = &array[3000];
fprintf(stderr, "Pointers are: %p, %p, %p\n" , a , b , c );
return 0;
}
Затем:
gcc -std=c99 -Wall -Werror -o foo foo.c
./foo
Выводит:
Pointers are: 0x7fff2c7d22c8, 0x7fff2c7d22c8, 0x7fff2c7d5180