Известно, что если не иметь оператора sizeof
то кол-во элементов массива возможно посчитать, например, так:
int arr[10];
size_t size = *(&arr + 1) - arr;
где
arr
есть указатель на первый элемент
&arr
есть указатель на весь массив
&arr + 1
есть указатель на следующий кусок памяти после нашего массива
*(&arr + 1)
есть адрес элемента который идет после последнего элемента массива.
и соответсвенно разность указателей даёт кол-во элементов между ними.
Вопросы:
Не приведёт ли это *(&arr + 1)
к UB
?
Каким образом &arr
есть указатель на весь массив ? Это определено стандартом ?
*(&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)
Однако этого пока сделано не было. То есть в языке С ваш вариант формально порождает неопределенное поведение.
В языке С++ все пока (и уже давно) подвешено в воздухе.
По стандарту оператор & берет адрес операнда и фактически превращает в массив из одного элемента.
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.
Виртуальный выделенный сервер (VDS) становится отличным выбором
Требуется преобразовать строку с кириллицей типа std::u16string к нижнему регистру
ListBig* self; выдаёт ошибку - error: 'ListBig' does not name a type ListBig* self;
Как легко удалить из строки начальные символы такие как пробел, табуляция или символ новой строки и тп
Если для Linux инструмент (или возможность в какой-либо ide) для просмотра оперативной памяти процесса, наподобие инструмента просмотра памяти...