Преобразование uint8_t* в uint32_t* в С++

Я пытаюсь реализовать функцию интерфейса библиотеки FATFS. Реализация ожидает uint8_t* для данных, которые должны быть записаны на внутреннюю SD-карту другой библиотекой. Данные должны быть записаны в библиотеку с помощью функции BSP_SD_WriteBlocks(uint32_t*, uint64_t, uint32_t, uint32_t). (Смотри ниже)

/*
 * Write data from a specific sector
 */
int SDMMC::disk_write(const uint8_t *buffer, uint32_t sector, uint32_t count)
{
    int res = BSP_SD_WriteBlocks((uint32_t*)buffer, (uint64_t)sector, 512, count);

    if (res == MSD_OK) {
        return RES_OK;
    } else {
        return RES_ERROR;
    }
}

Как видите, я пытаюсь привести 8-битный адрес памяти к 32-битному адресу памяти и не думаю, что это правильный способ сделать это.

К сожалению, я не могу изменить аргументы функции, поэтому функция disk_write должна принимать uint8_t*, а BSP_SD_WriteBlocks просто принимает uint32_t*.

Как лучше и быстрее всего это сделать?


person Alex van Rijs    schedule 27.01.2017    source источник
comment
Это действительный состав. Но аргумент count, вероятно, является количеством байтов (элементов uint8_t) для записи, верно? И если функция BSP_SD_WriteBlocks пишет 32-битные слова, то вам нужно разделить count на четыре, иначе она будет писать слишком много (и выйдет за пределы вашего буфера).   -  person Some programmer dude    schedule 27.01.2017
comment
Возможная проблема с последовательностью байтов. (и может нарушить строгое правило псевдонимов).   -  person Jarod42    schedule 27.01.2017


Ответы (2)


Хитрость заключается в том, чтобы изначально использовать массив uint32_t соответствующего размера (он может быть выделен динамически, см. ниже мою первую идею, но не обязательно). К любому объекту можно получить доступ на уровне байтов, поэтому вы можете преобразовать это uint32_t * в uint8_t * и обработать его как символьный буфер: вы получаете доступ к исходному массиву uint32_t на уровне байтов, что разрешено строгим правилом псевдонимов.

Когда вам понадобится uint32_t *, просто откиньте назад. Поскольку вы получили доступ к исходному массиву только на уровне байтов, время жизни массива не закончилось, и uint32_t * указывает на допустимый массив.


Старое и не очень хорошее решение

Хитрость здесь заключалась бы в том, чтобы выделить буфер с помощью malloc. Стандарт C говорит в части, ссылающейся на стандартную библиотеку C, которая явно доступна из программы C++ (*): 7.20.3 Функции управления памятью

... Указатель, возвращаемый в случае успешного выделения, соответствующим образом выровнен, чтобы его можно было назначить указателю на объект любого типа, а затем использовать для доступа к такому объекту или массиву таких объектов в выделенном пространстве...

Это означает, что при условии, что buffer является возвращаемым значением вызова malloc, стандарт гарантирует, что его можно безопасно привести к любому другому типу указателя.

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

Можно возразить, что здесь мы нарушаем строгое правило алиасинга. Но с ним подойдет любая обычная реализация (это нарушило бы слишком много существующего кода, чтобы отклонить его), и единственным строго совместимым способом было бы использование полной памяти буфера... чтобы закончить точно такой же последовательностью байтов с совместимый расклад!


(*) Я знаю, что C и C++ — разные языки, но стандартное справочное руководство C++ говорит в 1.2 Нормативные ссылки [intro.refs]

1 Для применения настоящего документа необходимы следующие ссылочные документы...
— ISO/IEC 9899:1999, Языки программирования — C
...
2 Библиотека, описанная в разделе 7 ISO/IEC 9899:1999 и пункт 7 ISO/IEC 9899:1999/Cor.1:2001 и пункт 7 ISO/IEC 9899:1999/Cor.2:2003 в дальнейшем называются стандартной библиотекой C.1
...

1) С оговорками, отмеченными в разделах с 18 по 30 и в C.3, стандартная библиотека C является подмножеством стандартной библиотеки C++.

person Serge Ballesta    schedule 27.01.2017

Здесь потенциально может быть несколько проблем.

  1. Вы отбрасываете const.

В педантичном мире это может привести к неопределенному поведению. Чтобы быть абсолютно правильным, предполагая, что вы не можете изменить BSP_SD_WriteBlocks, чтобы он принимал константный указатель, вам нужно будет сделать копию данных и использовать неконстантный указатель на вашу копию. Преимущество в том, что, делая копию, вы можете решить все остальные проблемы. Недостатком является то, что создание копии требует времени и памяти.

В практическом мире, если вы знаете, что BSP_SD_WriteBlocks никогда не пытается писать через этот указатель, вы, вероятно, в порядке. Но это очень важно, поэтому я бы использовал const_cast<> в стиле C++, чтобы было ясно, что вы делаете это намеренно.

  1. Выравнивание.

В педантичном мире приведение от std::uint8_t * к std::uint32_t * может быть небезопасным, по крайней мере, не переносимым, в зависимости от того, знаете ли вы, что исходный указатель правильно выровнен или ваша платформа разрешает невыровненный доступ. Обратите внимание, что копирование, предложенное в # 1, может решить эту проблему, так как вы можете легко убедиться, что ваш временный буфер правильно выровнен.

В практическом мире, если вы знаете, что исходный буфер всегда будет соответствующим образом выровнен, что кажется вероятным, то это не имеет большого значения. Опять же, я бы предложил приведение в стиле C++. Я бы также рекомендовал утверждать, что адрес буфера кратен размеру std::uint32_t.

person Adrian McCarthy    schedule 27.01.2017