Страницы

Поиск по вопросам

суббота, 21 декабря 2019 г.

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

#cpp #массивы #указатели


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

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


где 

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

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

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

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

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

Вопросы:


Не приведёт ли это *(&arr + 1) к UB ?  
Каким образом &arr есть указатель на весь массив ? Это определено стандартом ?

    


Ответы

Ответ 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) Однако этого пока сделано не было. То есть в языке С ваш вариант формально порождает неопределенное поведение. В языке С++ все пока (и уже давно) подвешено в воздухе.

Ответ 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.

Комментариев нет:

Отправить комментарий