Надежное определение типов в соответствии со стандартами C и C++

Есть ли способ ввести каламбур, который действителен как в C, так и в C++? Предпочтительно низкие накладные расходы и избегание тривиальных взломов препроцессора.

В C89 я знаю, что могу сделать что-то вроде этого:

unsigned int float_bits(float num) {
    return *(unsigned int *)#
}

Однако это нарушает строгое правило алиасинга C99. Так что что-то вроде этого может быть более переносимым для различных стандартов C:

unsigned int float_bits(float num) {
    union { float f; unsigned int i; } u;
    u.f = num;
    return u.i;
}

Но я знаю, что это недопустимый C++, потому что только один член объединения может быть «активным» в каждый момент времени. Типичное решение для C и C++ выглядит примерно так:

unsigned int float_bits(float num) {
    unsigned int i;
    memcpy(&i, &num, sizeof(int));
    return i;
}

Однако это зависит от того, сможет ли компилятор оптимизировать вызов memcpy. Является ли memcpy единственным методом, переносимым между стандартами C и C++?


person Ryan Avella    schedule 02.11.2019    source источник
comment
Хм, даже первый (unsigned int *)&num не надежен.   -  person chux - Reinstate Monica    schedule 02.11.2019
comment
memcpy это путь сюда. Или просто не занимайтесь каламбуром, он редко нужен :)   -  person DeiDei    schedule 02.11.2019
comment
Теперь, если вы опубликуете ответ, в котором говорится, что memcpy — единственный способ, мы можем использовать этот вопрос в качестве канонического дубликата для всех будущих вопросов этого типа.   -  person Richard Critten    schedule 02.11.2019
comment
@DeiDei Почему memcpy более (или менее) надежен, чем (unsigned int *)&num? (Оба потерпят неудачу, если sizeof(float) != sizeof(int).)   -  person Adrian Mole    schedule 02.11.2019
comment
@Adrian-ReinstateMonica только для C++: даже если sizeof(float) == sizeof(int), то *(unsigned int *)# является UB. memcpy допустим в этом случае, а static_assert можно использовать для проверки соответствия размеров.   -  person Richard Critten    schedule 02.11.2019
comment
@Adrian-ReinstateMonica: * (unsigned int *) &num нарушило бы правило C об использовании псевдонимов: компилятор имеет право предположить, что доступ через unsigned int * не дает доступа к объекту, который был определен как float, независимо от приведений. При оптимизации такой код, как num = 4; * (unsigned int *) &num = 0; printf("%d\n", num); printf("%d\n", num);, может печатать, среди прочего, «4» и «0», или «4» и «4», или «0» и «0».   -  person Eric Postpischil    schedule 02.11.2019
comment
@RichardCritten Я полагаю, что нашел решение, не связанное с memcpy, включающее приведение в стиле C к unsigned char *, что является исключением из строгого правила псевдонимов. Если мы хотим сделать это каноническим дубликатом для всех будущих вопросов, возможно, я мог бы сделать ответ сообщества-вики со всеми известными альтернативами каламбуру и их соответствующими плюсами и минусами?   -  person Ryan Avella    schedule 03.11.2019
comment
@RyanAvella: Вы можете прочитать свой unsigned char*, но как извлечь из него какой-либо другой тип значения? Похоже, вы просто в конечном итоге реализуете memcpy самостоятельно.   -  person Davis Herring    schedule 03.11.2019
comment
@RyanAvella имейте в виду, что приведение to к символьному типу позволяет получить доступ к байтам какого-либо другого типа, но не наоборот. Нельзя обойти приведение промежуточным приведением к char *   -  person dbush    schedule 03.11.2019
comment
Первый код также неверен в C89 (у которого есть строгое правило псевдонимов).   -  person M.M    schedule 04.11.2019
comment
Эффективный тип @curiousguy актуален только для пространства malloc, что не относится к этому вопросу. Эффективный тип переменной, объявленной как T, всегда равен T независимо от memcpying.   -  person M.M    schedule 04.11.2019
comment
@MM: Насколько я могу судить, никогда не было единого мнения относительно того, когда именно объекты могут принимать эффективный тип, отличный от их объявленного типа. В данном конкретном случае они бы этого не сделали, но если функции передается адрес объекта статической или автоматической длительности, у нее не будет возможности узнать объявленный тип этого объекта, даже если он должен быть.   -  person supercat    schedule 01.04.2020


Ответы (1)


unsigned int float_bits(float num) {
  unsigned int i;
  memcpy(&i, &num, sizeof(int));
  return i;
}

нет никакого побочного эффекта от этого вызова memcpy, кроме изменения i, чтобы иметь те же байты, что и num.

Это правда, что компиляторы могут вставлять сюда вызов библиотечной memcpy функции. Они также могут бесплатно вставить 1 миллион NOOP, тренировочную сессию искусственного интеллекта с моделированием понга и попытаться найти место для доказательства гипотезы Голдблаха.

В какой-то момент вы должны предположить, что ваш компилятор не враждебен.

Каждый приличный компилятор понимает, что делает memcpy.

person Yakk - Adam Nevraumont    schedule 02.11.2019
comment
в C: Изменяет ли это эффективный тип? - person curiousguy; 03.11.2019
comment
@curiousguy Нет. - person Language Lawyer; 03.11.2019