тонкости указателя на массив

133
04 июля 2019, 08:20

Известно, что если не иметь оператора sizeof то кол-во элементов массива возможно посчитать, например, так:

int arr[10];
size_t size = *(&arr + 1) - arr;

где

arr есть указатель на первый элемент

&arr есть указатель на весь массив

&arr + 1 есть указатель на следующий кусок памяти после нашего массива

*(&arr + 1) есть адрес элемента который идет после последнего элемента массива.

и соответсвенно разность указателей даёт кол-во элементов между ними.

Вопросы:

  1. Не приведёт ли это *(&arr + 1) к UB ?

  2. Каким образом &arr есть указатель на весь массив ? Это определено стандартом ?

Answer 1

*(&arr + 1) может быть записано как (&arr)[1] или как 1[&arr]. Именно в таком "более интересном" виде этот вопрос периодически всплывает в обсуждениях.

В С++ формального ответа на этот вопрос не существует. Тема когда-то активно обсуждалась, но так и застряла в состоянии "drafting":

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#232

Никаких поползновений дать окончательный ответ на этот вопрос пока не видно. То есть до сих пор в языке С++ нет ответа на давний вопрос о том, можно ли делать так

int arr[10];
for (int *p = &arr[0]; p != &arr[10]; ++p) // `&arr[10]` - UB или нет?
  ...;
for (int *p = arr; p != 1[&arr]; ++p) // `1[&arr]` - UB или нет?
  ...;

Вопрос о формальной легальности по крайней мере первого варианта известен еще со времен Царя Гороха, но вменяемого ответа на него до сих пор не предоставили.

В языке С предприняли попытки разрешить часть таких ситуаций, объявив соседние операторы & и * "аннигилирующими" друг друга еще до начала вычисления выражения. Это легализовало вариант &arr[10]

&arr[10]  <=>  &*(arr + 10)   <=>  arr + 10 - нет UB

Но это формально не легализовало вариант 1[&arr] (ваш вариант). В этом варианте мы имеем

 (int *) *(&arr + 1)
   ^     ^^^^^^^^^^^ 
   |     выражение, результат которого имеет тип `int [10]`
   |
   стандартное неявное преобразование массива к указателю 

Точно так же, как язык С объявил соседние & и * "аннигилирующими" друг друга, надо было бы соседнее "неявное преобразование массива к указателю" и оператор * объявить "коллапсирующим" до просто преобразования указателя, т.е. считать это выражение эквивалентным

 (int *) (&arr + 1)

Однако этого пока сделано не было. То есть в языке С ваш вариант формально порождает неопределенное поведение.

В языке С++ все пока (и уже давно) подвешено в воздухе.

Answer 2

По стандарту оператор & берет адрес операнда и фактически превращает в массив из одного элемента.

For purposes of pointer arithmetic ([expr.add]) and comparison ([expr.rel], [expr.eq]), an object that is not an array element whose address is taken in this way is considered to belong to an array with one element of type T.

Далее простая адресная арифметика. В данном случае элементом массива является массив из десяти элементов целого числа. Поэтому инкрементирование указателя приведет к переходу на следующий элемент массива. После чего остается только взять указатель на первый элемент следующего элемента (извините за тавтологию) и получить их разницу. Разименование *(&arr + 1) даст указатель на первый элемент следующего массива int arr[10];, а по сути указатель останется тот же, только с типом int*.

Указатель и то, на что он указывает это разные вещи. Место в памяти не имеет никакого значения, указатель можно инкрементировать сколько угодно. В данном случае работа идет с обычным числом, которое является указателем:

int arr[10];
auto sp = &arr;
sp++;
sp++;
sp++;
sp++;
//size_t size = *(&arr + 1) - arr;
size_t size = (int*)sp - arr; // По сути тоже самое

Судя по стандарту все нормально. Ни к какому UB это не приведет. И да, в стандарте это все определено.

Чтобы лучше продемонстрировать можно взять более простой тип:

int a;
int *pa = &a;
int *pb = pa + 1;
size_t s = pb - pa;

if P and Q point to, respectively, elements x[i] and x[j] of the same array object x, the expression P - Q has the value i − j.

В данном случае получим размерность массива, которая будет равна единице. Если нужно получить размерность типа тогда можно сделать следующее:

size_t s = (char*)pb - (char*)pa;

Но лучше не изобретать велосипед и использовать sizeof.

READ ALSO
Преобразование строки типа std::u16string к нижнему регистру

Преобразование строки типа std::u16string к нижнему регистру

Требуется преобразовать строку с кириллицей типа std::u16string к нижнему регистру

118
error: does not name a type

error: does not name a type

ListBig* self; выдаёт ошибку - error: 'ListBig' does not name a type ListBig* self;

162
Как легко удалить из строки начальные символы такие как пробел, табуляция или символ новой строки и т.п. а также в конце?

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

Как легко удалить из строки начальные символы такие как пробел, табуляция или символ новой строки и тп

153
Просмотр памяти Linux [закрыт]

Просмотр памяти Linux [закрыт]

Если для Linux инструмент (или возможность в какой-либо ide) для просмотра оперативной памяти процесса, наподобие инструмента просмотра памяти...

135