В одном из своих прошлых постов я рассказывал, как компилировать приложения, использующую библиотеку GTK. Однако, как оказалось, входящая в ее состав библиотека GLib имеет одну неприятную особенность.
Как-то я писал небольшую программу с ее использованием. Она нормально компилировалась и работала. Однако при попытке скомпилировать ее с ключом -m32 (создать 32-битное приложение) я получил ошибку и несколько предупреждений:
Текст ошибки гласил: «размер массива _GStaticAssertCompileTimeAssertion_0 меньше нуля». Что это за массив такой? В моей программе его нет.
Из текста ошибки ясно, что она произошла в функции _GLIB_CHECKED_ADD_U64, которая объявлена в модуле gtypes.h. Ниже приводится исходный код этой функции:
static inline gboolean _GLIB_CHECKED_ADD_U64 ( guint64 *dest, guint64 a, guint64 b) { G_STATIC_ASSERT(sizeof (unsigned long long) == sizeof (guint64)); return !__builtin_uaddll_overflow(a, b, (unsigned long long *) dest); }
Ошибку компиляции вызывает макрос G_STATIC_ASSERT. Он объявлен в заголовочном файле gmacros.h. Массив _GStaticAssertCompileTimeAssertion_0 является частью его реализации. В этом легко убедиться, изучив код макроса.
По сути G_STATIC_ASSERT является аналогом оператора static_assert в С++. Посмотрим, какое условие он проверяет, и почему оно не выполняется при компиляции под 32 бита.
sizeof (unsigned long long) == sizeof (guint64)
Здесь проверяется соответствие типов. К сожалению, я не нашел, где вводится тип guint64. Но для понимания причины происходящего нам достаточно той информации, что приведена в документации (доступна по ссылке). Там guint64 описывается как:
«An unsigned integer guaranteed to be 64-bits on all platforms»
или в моём переводе:
«Беззнаковое целое имеющее размер 64 бита на всех платформах».
Это условие и проверяется макросом в нашей функции. Вроде бы все логично и все правильно. Но там же, в документации, приводится и описание того, как вводится тип guint64:
typedef unsigned long guint64;
Получается, что guint64 — это аналог типа unsigned long, но сравнивается он с типом unsigned long long. Давайте разбираться с типом long. Сразу оговорюсь, стандарт С++ не уточняет размер типа, он говорит только о диапазоне значений, которые могут в нем храниться.
Как показывает мой опыт и информация в интернете, в системах Linux размер типа long эквивалентен размеру типа size_t. То есть, на 32-битных машинах он равен 32 битам, а на 64-битных — 64 битам. Поэтому и возникает ошибка.
Когда я компилирую 64-битное приложение, тип long (а значит и guint64) имеет размер 64 бита, его размер равен размеру типа long long, и компиляция проходит нормально. Когда же я компилирую 32-битное приложение, тип long имеет размер 32 бита, условие в макросе G_STATIC_ASSERT не выполняется и компиляция прерывается.
Таким образом получается, что GLib в действительности не обеспечивает размер типа guint64 равным 64 битам на «всех платформах». И это огорчает.