У меня есть указатель на массив, и я хочу использовать такие функции, как memcpy, с определенным смещением, однако при смещении адреса указателя я получаю значение, превышающее смещение, и я не понимаю, почему. Может кто-нибудь объяснить, что здесь происходит?
#include <stdio.h>
#include <stdint.h>
int main()
{
uint8_t *source[5];
// Initial
printf("%p\n", (void *)source); // 786796896
// Offset by 2
printf("%p\n", (void *)(source + 2)); // 786796912 (unexpected, more than 2)
}
@pmacfarlane Ну, если 11 не плохо, то насколько хорошо 0?
%d — неправильный способ печати указателя. Попробуйте %p.
uint8_t *ptr = source; — это ошибка, как вам скажет ваш компилятор. @user16217248 имеет объяснение значений, которые вы видите, но это, вероятно, неопределенное поведение, которое просто делает то, что вы хотите.
Спасибо, что указали на предупреждения, я использовал онлайн-компилятор, когда делал пример, и не видел их. Я исправил предупреждения, упростил пример и обновил printf, чтобы использовать %p для указателей на @pmacfarlane.





Проблема здесь в том, что когда вы добавляете 2 к source, массив распадается на тип указателя uint8_t **. Когда вы выполняете арифметические действия с указателем, добавленное смещение представляет собой количество добавленных элементов, а не количество байтов, если размер элемента указателя больше байта. Смещение в байтах от source + 2 на самом деле равно 2*sizeof(*source) байтам, то есть 16.
Чтобы обойти это поведение, приведите source к char *, выполните сложение, а затем выполните обратное приведение. Однако имейте в виду, что неправильное выполнение может привести к невыровненному доступу, что может быть плохой новостью.
Это также UB для создания невыровненного указателя
@M.M Значит, невыровненные указатели являются представлениями-ловушками?
Не совсем. Преобразование указателя в другой тип указателя с неправильным выравниванием — это UB ; и преобразование целого числа в указатель с неправильным выравниванием определяется реализацией и может привести к представлению ловушки. (Понятия не имею, почему несоответствие между этими правилами, вероятно, исторические причины)
@M.M Ну, а что, если я сначала преобразую указатель в целое число, а затем преобразую в другой указатель? Простой обход UB?
Как это сделает указатель выровненным после этого?
@ Герхард Это не так. Но по крайней мере это не будет мгновенный UB.
Это точно то же самое. Создание невыровненного указателя тем или иным способом дает тот же результат. Как только вы присваиваете неверное значение указателю, вы ничем не лучше, чем раньше. Вы только добавили дополнительный шаг без какой-либо пользы между ними.
Что ж, представление-ловушка будет «срабатывать» при сохранении результата второго приведения, если оно на самом деле было представлением-ловушкой. Но реализация может определить невыровненные значения, чтобы они не были представлениями-ловушками.
Хммм... тут нет распада... объект совсем другой, это массив из пяти указателей, а не указатель на массив из пяти int8_t. Поэтому, пожалуйста, не путайте ЗП с неправильным ответом.
Затухание до указателя происходит только для переменных параметров, объявленных как массивы, и только на первом уровне, поэтому определение параметра, например double matrix[3][5], распадается на double (*matrix)[5], А НЕ НА double ***matrix или любую другую вашу идею о распаде.
в этом случае объявленный объект вопроса является не указателем, а массивом указателей, и затухание не выполняется, поскольку это локальная переменная, а не параметр функции. В переменной есть пять неинициализированных указателей на int8_t, но ни одного указателя на массив. Мы сумасшедшие? есть целая глава (или даже больше), посвященная несколько неясному синтаксису выражений определения типа в K&R. Пожалуйста, прочитайте это.
@LuisColorado Я имел в виду, что когда вы добавляете x к массиву a, это эквивалентно получению адреса элемента в a[x] так же, как если бы массив был просто указателем на его первый элемент.
@user16217248 user16217248, когда вы используете имя массива (необработанное, оператор субиндекса не используется) как часть выражения, это означает ссылку указателя на первый элемент массива. Вы не можете ссылаться на массив в C, но только для использования sizeof оператор для расчета его размера. Итак, вы имели в виду указатель на первый элемент, добавленный двумя, который указывает на две позиции uint8_t впереди array. Это просто арифметика указателя, а не затухание. Хуже, когда вы говорите что-то вроде 2 + array, что означает то же самое, и делает выражение array[2] эквивалентным 2[array]. Затемнять!
array сам по себе в выражении является rvalue типа uint8_t *, который отличается от типа массива (это uint8_t*[5], как указано в вопросе, или как uint8_t(*)[5], поскольку предлагаемое объявление должно было иметь указатель на массив ) Это изменяет фактический тип выражения array и делает значение, переданное printf(), другим.
Указатель Arthimetics нужно избегать, насколько это возможно. Для выше
#include <stdio.h>
#include <stdint.h>
int main() {
uint8_t* source[5]; // array of 5 pointers of uint8_t* type
printf("%p\n", &source[2]); // address of 3rd element place in array source
}
Очень важно отметить, что добавление 2 к исходному адресу не приводит к увеличению адреса на +2, а на +10, потому что 2 интерпретируется как char* [5], а не char *.
// using without casting
char * arr[5];
char * parr = malloc(sizeof(int));
printf("%p\t%p\n", arr, parr);
printf("%p\t%p\n", arr+2, parr+2);
0x7ffde2925fb0 0x55b519f252a0
+10 +2
0x7ffde2925fc0 0x55b519f252a2
//using with casting
char * arr[5];
char * parr = malloc(sizeof(int));
printf("%p\t%p\n", arr, parr);
printf("%p\t%p\n", (void*)arr+2, parr+2);
0x7ffde2925fb0 0x55b519f252a0
+2 +2
0x7ffde2925fb2 0x55b519f252a2
Тогда как правильно использовать source[2] в контексте memcpy?
каков вариант использования? Также источник представляет собой массив указателей, вам сначала нужно выделить память для элементов, которые вы собираетесь использовать.
Я не вижу преимущества в том, чтобы изо всех сил избегать арифметики указателей. И этого на самом деле трудно избежать, особенно если вы когда-либо использовали массивы. Потому что индексация массива — это арифметика указателей + разыменование.
Прямой отказ от арифметики с указателями — это не норма, а, я думаю, хорошо известный факт в сообществе.
Учитывая, что синтаксис массива — это просто еще один способ записи арифметических операций с указателями, я не вижу причин строго его избегать. &source[2] точно так же, как source+2. И в этом случае я нахожу последний даже немного более читабельным, чем использование синтаксиса массива. Кстати: вы вызываете неопределенное поведение, читая неопределенные значения ваших элементов указателя. И вы забыли привести к void*, который, как ожидается, будет передан для %p.
Хорошо, это работает, но мне не нравится приведение: memcpy((uint8_t *)buf_header + offset1 + offset2, val, val_size);
Арифметики с указателями нужно избегать настолько, насколько это возможно, это плохой совет для изучающих язык C. Если вы не понимаете арифметики указателей, пожалуйста, не путайте людей своими утверждениями. Вы не поняли, что int8_t *source[5] ЯВЛЯЕТСЯ НЕ указателем на массив, а массивом указателей (совсем другой объект).
@JohnBollinger, индексация массива - это замаскированный способ выполнения арифметических операций с указателями. Вы правы, так что не вините меня, я также добавил комментарий по этому поводу, поэтому, пожалуйста, прочитайте его, прежде чем жаловаться.
У меня есть указатель на массив
uint8_t *source[5];
Извините, но это не указатель на массив. Это массив из 5 указателей на uint8_t, неинициализированный, поэтому вы должны его инициализировать.
Это указатель на массив:
uint8_t (*source)[5];
(приоритет оператора [] выше, чем у * в выражениях типа)
...Что здесь происходит?
ну, исходя из вашего неверного предположения, все ожидаемо будет неверным.
// Initial
printf("%p\n", (void *)source); // 786796896
// Offset by 2
printf("%p\n", (void *)(source + 2)); // 786796912 (unexpected, more than 2)
source + 2 — это адрес третьего элемента массива source, который представляет собой массив указателей, поэтому два указателя смещаются от самого source (это 16 байт в 64-битной архитектуре — значение, которое вы получаете — и 8 байт в 32-битной архитектуре). архитектуры)
Просто попробуйте с предложенной декларацией, и вы получите то, что ожидаете.
$ a.out
0x560bee9df060
0x560bee9df06a
$ _
(10 позиций, как и ожидалось для двух массивов из 5 uint8_t элементов)
Но вы можете проверить это сами.
Если вы используете оператор sizeof для самого source, вы получите размер массива (5 указателей или 40 байтов в 64-битной архитектуре), в то время как, как вы указываете в своем вопросе, он должен быть размером с один указатель (8 байтов , в 64-битной арке) Если вы объявите его с помощью предложенного объявления, то вы получите 8, как и должно быть expetec, а если вы сделаете sizeof *source (размер объекта, на который указывает source), в этом случае вы получите 5 (размер массива из пяти значений uint8_t) Попробуйте и убедитесь.
Заключительное замечание об обсуждении выравнивания, рассмотренном в комментариях к вопросу.
uint8_t, поэтому его адрес (значение, переданное printf, действительно выровнено и действительно (это действительно адрес в стеке, где была создана переменная source). ), и при использовании этого адреса не возникает UB, потому что содержимое массива само по себе не используется, поэтому он остается неинициализированным и не вызывает UB.)(void *) само по себе обычно не вызывает UB.)uint8_t с выравниванием 1 (единица), поэтому любой адрес должен быть приемлемым. Поскольку указатель не разыменовывается, он ведет себя как любая неинициализированная переменная и, таким образом, не вызывает U.B.
Мой компилятор (с настройками по умолчанию) выдает 7 предупреждений. Неплохо для 20-строчной программы.