Более чистый арифметический синтаксис указателя для манипуляций с байтовыми смещениями
-
06-07-2019 - |
Вопрос
В следующих строках кода мне нужно настроить указатель pm
на смещение в байтах в одном из его полей. Есть ли лучший / более простой способ сделать это, чем непрерывное приведение назад и вперед из char *
и PartitionMap *
, чтобы арифметика указателей все еще работала?
PartitionMap *pm(reinterpret_cast<PartitionMap *>(partitionMaps));
for ( ; index > 0 ; --index)
{
pm = (PartitionMap *)(((char *)pm) + pm->partitionMapLength);
}
return pm;
Для тех, кто не может выйти из кода, он просматривает дескрипторы переменной длины в буфере, которые наследуются от PartitionMap
.
Также для заинтересованных лиц, partitionMapLength всегда возвращает длины, которые поддерживаются системой, на которой он работает. Данные, которые я просматриваю, соответствуют UDF спецификации.
Решение
Я часто использую эти шаблоны для этого:
template<typename T>
T *add_pointer(T *p, unsigned int n) {
return reinterpret_cast<T *>(reinterpret_cast<char *>(p) + n);
}
template<typename T>
const T *add_pointer(const T *p, unsigned int n) {
return reinterpret_cast<const T *>(reinterpret_cast<const char *>(p) + n);
}
Они поддерживают тип, но добавляют к ним отдельные байты, например:
T *x = add_pointer(x, 1); // increments x by one byte, regardless of the type of x
Другие советы
Приведение типов - единственный способ, будь то тип char *, intptr_t или другой подобный тип, а затем - конечный тип.
Конечно, вы можете просто хранить две переменные: char *
для пошагового перемещения по буферу и PartitionMap *
для доступа к нему. Делает это немного яснее, что происходит.
for (char *ptr = ??, pm = (PartitionMap *)ptr ; index > 0 ; --index)
{
ptr += pm->partitionMapLength;
pm = (PartitionMap *)ptr;
}
return pm;
Как уже упоминали другие, вам нужны приведения, но вы можете скрыть уродство в макросе или функции. Однако следует помнить еще одну вещь - требования к выравниванию. На большинстве процессоров вы не можете просто увеличить указатель на тип на произвольное число байтов и преобразовать результат обратно в указатель на исходный тип без проблем с доступом к структуре через новый указатель из-за смещения.
Одной из немногих архитектур (даже если она самая популярная), которая позволит вам сойти с рук, является архитектура x86. Тем не менее, даже если вы пишете для Windows, вам нужно принять во внимание эту проблему - Win64 действительно соблюдает требования выравнивания.
Таким образом, даже доступ к элементу partitionMapLength
через указатель может привести к сбою вашей программы.
Вы можете легко обойти эту проблему, используя расширение компилятора, например __ unaligned
в Windows:
PartitionMap __unaliged *pm(reinterpret_cast<PartitionMap *>(partitionMaps));
for ( ; index > 0 ; --index)
{
pm = (PartitionMap __unaligned *)(((char *)pm) + pm->partitionMapLength);
}
return pm;
Или вы можете скопировать потенциально невыровненные данные в правильно выровненную структуру:
PartitionMap *pm(reinterpret_cast<PartitionMap *>(partitionMaps));
char* p = reinterpret_cast<char*>( pm);
ParititionMap tmpMap;
for ( ; index > 0 ; --index)
{
p += pm->partitionMapLength;
memcpy( &tmpMap, p, sizeof( newMap));
pm = &tmpMap;
}
// you may need a more spohisticated copy to return something useful
size_t siz = pm->partitionMapLength;
pm = reinterpret_cast<PartitionMap*>( malloc( siz));
if (pm) {
memcpy( pm, p, siz);
}
return pm;
Приведение должно быть выполнено, но это делает код почти нечитаемым. Для удобства чтения выделите его в функции static inline
.
Что меня озадачивает, так это то, что у вас есть 'partitionMapLength' в байтах?
Разве не было бы лучше, если бы он был в единицах 'partitionMap', поскольку вы все равно разыгрывали его?
PartitionMap *pmBase(reinterpret_cast<PartitionMap *>(partitionMaps));
PartitionMap *pm;
...
pm = pmBase + index; // just guessing about your 'index' variable here
И C, и C ++ позволяют выполнять итерацию по массиву с помощью указателей и ++
:
#include <iostream>
int[] arry = { 0, 1, 2, 3 };
int* ptr = arry;
while (*ptr != 3) {
std::cout << *ptr << '\n';
++ptr;
}
Чтобы это работало, добавление к указателям определяется, чтобы взять адрес памяти, сохраненный в указателе, а затем добавить размер любого типа, умноженного на добавляемое значение. Например, в нашем примере ++ ptr
добавляет 1 * sizeof (int)
к адресу памяти, хранящемуся в ptr
.
Если у вас есть указатель на тип и вы хотите выдвинуть определенное количество байтов из этого места, единственный способ сделать это - привести к char *
(потому что sizeof (char)
определяется как один).