Более чистый арифметический синтаксис указателя для манипуляций с байтовыми смещениями

StackOverflow https://stackoverflow.com/questions/1623010

Вопрос

В следующих строках кода мне нужно настроить указатель 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) определяется как один).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top