Установите значение FourCC в C++
Я хочу установить значение FourCC в C++, то есть 4-байтовое целое число без знака.
Я полагаю, что очевидным способом является #define, например
#define FOURCC(a,b,c,d) ( (uint32) (((d)<<24) | ((c)<<16) | ((b)<<8) | (a)) )
а потом:
uint32 id( FOURCC('b','l','a','h') );
Какой самый элегантный способ сделать это?
12 ответов
Вы можете сделать это постоянной времени компиляции, используя:
template <int a, int b, int c, int d>
struct FourCC
{
static const unsigned int value = (((((d << 8) | c) << 8) | b) << 8) | a;
};
unsigned int id(FourCC<'a', 'b', 'c', 'd'>::value);
Приложив немного дополнительных усилий, вы можете проверить во время компиляции, что каждое переданное число находится в диапазоне от 0 до 255.
uint32_t FourCC = *((uint32_t*)"blah");
Почему не это?
РЕДАКТИРОВАТЬ: int -> uint32_t.
И нет, это не бросает символ ** в uint32_t. Он преобразует (char *) в (uint32_t *), а затем разыменовывает (uint32_t *). В этом нет никакого порядка байтов, так как он назначает uint32_t для uint32_t. Единственными недостатками являются выравнивание, и я не указал явно 32-битный тип.
С помощью C++11 constexpr вы можете написать что-то вроде:
constexpr uint32_t fourcc( char const p[5] )
{
return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
}
И затем используйте это как:
fourcc( "blah" );
плюсы:
- Более читабельным,
- если строковый аргумент известен во время компиляции, то функция оценивается во время компиляции (без накладных расходов во время выполнения).
- не зависит от порядкового номера (то есть первый символ аргумента всегда будет в старшем байте fourcc).
минусы:
- Требуется компилятор C++11 (или новее).
Или сделать то же самое с помощью встроенной функции
inline uint32_t FOURCC(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
{
return ( (uint32) (((d)<<24) | (uint32_t(c)<<16) | (uint32_t(b)<<8) | uint32_t(a)) )
}
и избежать головной боли макроса, но в противном случае ваш подход выглядит хорошо для меня.
Если я не ошибаюсь, вы можете просто использовать многозначные символьные константы для этого права?
unsigned int fourCC = 'blah';
Это совершенно верно для спецификации ANSI/ISO, хотя некоторые компиляторы будут немного жаловаться. Вот как типы ресурсов раньше обрабатывались в более старых API Macintosh.
Если постоянная времени компиляции не требуется, возможно, самая
unsigned int FourCCStr(const char (&tag)[5])
{
return (((((tag[3] << 8 ) | tag[2]) << 8) | tag[1]) << 8) | tag[0];
}
#define FOURCC(tag) FourCCStr(#tag)
unsigned int id(FOURCC(blah));
Это принимает только теги из четырех символов, как требуется.
Предполагая Windows (так как FOURCC является концепцией Windows), Win API уже предоставляет mmioStringToFOURCC и mmioFOURCC.
Я не вижу ничего плохого в вашем алгоритме. Но для чего-то подобного я бы просто написал функцию вместо макроса. Макросы содержат множество скрытых функций / проблем, которые могут со временем вас укусить.
uint FourCC(char a, char b, char c, char d) {
return ( (uint32) (((d)<<24) | ((c)<<16) | ((b)<<8) | (a)) );
}
Вместо #define я бы, вероятно, поместил практически такой же код и использовал бы встроенный компилятор.
В настоящее время есть лучшее решение с использованием constexpr и С++17 (может быть, раньше, не уверен). Я не уверен, что это полностью кроссплатформенная платформа, но она работает в Visual Studio и XCode.
Во-первых, вам нужна функция-оболочка для преобразования функций в значения времени компиляции:
template <class TYPE, TYPE VALUE> constexpr TYPE CompileTimeValue() { return VALUE; }
Затем вам нужна функция constexpr для преобразования короткой строки в целое число:
template <class UINT, UInt32 IS_LITTLE_ENDIAN> constexpr UINT MakeCCFromNullTerminatedString(const char * string)
{
UINT cc = 0;
UINT shift = 1;
if (IS_LITTLE_ENDIAN)
{
shift = (sizeof(UINT) == 8) ? (0xFFFFFFFFFFFFFFull + 1) : 0xFFFFFF + 1;
}
while (UINT c = *string++)
{
c *= shift;
cc |= c;
if (IS_LITTLE_ENDIAN)
{
shift /= 256;
}
else
{
shift *= 256;
}
}
return cc;
}
Затем оберните макросы, чтобы иметь как 4-байтовые, так и 8-байтовые символьные константы, с вариантами младшего и старшего порядка байтов (если хотите)...
#define ID32(x) CompileTimeValue<UInt32,MakeCCFromNullTerminatedString<UInt32,0>(x)>()
#define ID64(x) CompileTimeValue<UInt64,MakeCCFromNullTerminatedString<UInt64,0>(x)>()
#define CC32(x) CompileTimeValue<UInt32,MakeCCFromNullTerminatedString<UInt32,1>(x)>()
#define CC64(x) CompileTimeValue<UInt64,MakeCCFromNullTerminatedString<UInt64,1>(x)>()
Несколько тестов для проверки..
ASSERT(CC32("test") == 'test');
UInt32 v = CC32("fun");
UInt32 test;
switch (v)
{
case CC32("fun"):
test = 1;
break;
case CC32("with"):
test = 2;
break;
case CC32("4ccs"):
test = 3;
break;
}
Проверка превышения границ не выполняется, хотя, возможно, ее можно добавить с помощью утверждений времени компиляции.
Как насчет:
#if BYTE_ORDER == BIG_ENDIAN
#define FOURCC(c0,c1,c2,c3) ((uint32) ((((uint32)((uint8)(c0)))<<24) +(((uint32)((uint8)(c1)))<<16)+ (((uint32)((uint8)(c2)))<<8) + ((((uint32)((uint8)(c3))))))
#else
#if BYTE_ORDER == LITTLE_ENDIAN
#define FOURCC(c3,c2,c1,c0) ((uint32) ((((uint32)((uint8)(c0)))<<24) +(((uint32)((uint8)(c1)))<<16)+ (((uint32)((uint8)(c2)))<<8) + ((((uint32)((uint8)(c3))))))
#else
#error BYTE_ORDER not defined
#endif
#endif
uint32 fcc(char * a)
{
if( strlen(a) != 4)
return 0; //Unknown or unspecified format
return
(
(uint32)
(
((*(a+3))<<24) |
((*(a+2))<<16) |
((*(a+1))<<8) |
(*a)
)
);
}