Арифметика указателя в memcpy имеет странный результат

Я возвращаюсь к программированию на C через несколько лет, поэтому я думаю, что я немного заржавел, но я вижу какое-то странное поведение в своем коде.

У меня есть следующее:

memcpy(dest + (start_position * sizeof(MyEnum)), source, source_size * sizeof(MyEnum));

Где:

  • dest и source представляют собой массивы MyEnum разных размеров,
  • dest имеет длину 64 байта.
  • source имеет длину 16 байт.
  • sizeof(MyEnum) составляет 4 байт
  • source_size равно 4, так как внутри массива 4 перечисления.

Я зацикливаю этот код 4 раза, каждый раз продвигая start_position, поэтому на каждой из 4 итераций цикла я получаю вызов memcpy со следующими значениями (я уже проверил это с помощью отладчика):

  1. memcpy(dest + (0), source, 16); (start_position = 0 * 4, так как размер source равен 4)
  2. memcpy(dest + (16), source, 16); (start_position = 1 * 4, так как source размер равен 4)
  3. memcpy(dest + (32), source, 16); (start_position = 2 * 4, так как source размер равен 4)
  4. memcpy(dest + (48), source, 16); (start_position = 3 * 4, так как source размер равен 4)

memcpy отлично работает в первом цикле, но во втором вместо этого копирует данные в другой массив, явно выходя за пределы области памяти массива dest, нарушая область памяти другого массива.

Поэтому я проверил арифметику указателя, происходящую внутри моей функции, и вот что я получил:

  • dest адрес 0xbeffffa74
  • dest + (start_position * sizeof(MyEnum)) равно 0xbefffab4 для (start_position * sizeof(MyEnum) = 16
  • Нарушенный массив находится по адресу 0xbefffab4.

Хотя это объясняет, почему память массива нарушается, я не понимаю, как 0xbeffffa74 + 16 будет 0xbefffab4, но я могу подтвердить, что это адрес, по которому вызывается memcpy.

Я запускаю это на Raspberry Pi, но, насколько мне известно, это не имеет значения.


person Michel Feinstein    schedule 15.09.2019    source источник


Ответы (1)


Арифметика указателя работает с размером указанного типа данных. Если у вас есть char*, то каждый раз, когда вы увеличиваете указатель, он будет двигаться на единицу. Если это int*, то каждое приращение добавляет к указателю больше единицы, обычно 4 (из-за того, что int обычно, но не всегда, является 32-битным).

Если у вас есть указатель на структуру, то увеличение указателя перемещает его на размер структуры. Поэтому sizeof там быть не должно, иначе вы будете слишком много двигаться.

memcpy(dest + (start_position * sizeof(MyEnum)), source, source_size * sizeof(MyEnum));

Это перемещает указатель на 4*4 байта в каждой позиции, поскольку MyEnum составляет четыре байта.

memcpy(dest + start_position, source, source_size * sizeof(MyEnum));

Это перемещает его только на 4 байта за раз.

Это логично, потому что pointer[2] совпадает с *(pointer + 2), поэтому, если бы арифметика указателей не учитывала неявно указанный размер типа, для всех индексов также потребовался бы sizeof, и в итоге вы написали бы много pointer[2 * sizeof(*pointer)].

person Sami Kuhmonen    schedule 15.09.2019
comment
a[b] совпадает с *(a+b). Это означает, что (a+b) должно совпадать с &a[b]. Теперь рассмотрим int a[2];. Если вам нужен второй элемент a, вы должны использовать a[1], а не a[1* sizeof(int)]. Это означает *(a+1), а не *(a+1*sizeof(int)). Таким образом, для адреса второго элемента (a+1), а не (a+1*sizeof(int)). Так почему вы умножаете на sizeof(myEnum), если вы не умножаете на sizeof(int)? - person David Schwartz; 15.09.2019
comment
Да, смотреть с этой точки зрения имеет смысл, я просто забыл о классическом *(a+b) и просто думал о байтах. Жаль, что мне не проголосовали за этот вопрос, так как ответ здесь кажется намного лучше, чем ответы на дубликаты. - person Michel Feinstein; 15.09.2019