Эффективные данные преобразования одного целочисленного типа в другое с тем же представлением

Большинство компиляторов микрокомпьютера C имеют два знаковых целочисленных типа с одинаковым размером и представлением вместе с двумя такими неподписанными типами. Если int - 16 бит, его представление будет в целом соответствовать short; если long - 64 бита, он будет в целом соответствовать long long; в противном случае int и long обычно будут иметь сопоставимые 32-битные представления.

Если на платформе, где long, long long и int64_t имеют одинаковое представление, необходимо передать буфер в три функции API по порядку (предположим, что API-интерфейсы находятся под контролем кого-то другого и используют указанные типы, если функции могут быть легко изменены, их можно просто изменить, чтобы использовать один и тот же тип).

void fill_array(long *dat, int size);
void munge_array(int64_t *dat, int size);
void output_array(long long *dat, int size);

существует ли какой-либо эффективный стандартно-совместимый способ разрешить все три функции использовать один и тот же буфер, не требуя, чтобы все данные скопировать между вызовами функций? Я сомневаюсь, что авторы правил сглаживания C предполагали, что такая вещь должна быть сложной, но для современных компиляторов модно считать, что ничего, написанное через long*, не будет прочитано через long long*, даже если эти типы имеют одинаковые представление. Кроме того, в то время как int64_t обычно будет таким же, как либо long, либо long long, реализации не соответствуют друг другу.

В компиляторах, которые не агрессивно используют псевдонимы на основе типов через вызовы функций, можно просто наложить указатели на соответствующие типы, возможно, включая статическое утверждение, чтобы гарантировать, что все типы имеют одинаковый размер. Проблема заключается в том, что если компилятор вроде gcc после разворачивания вызовов функций видит, что какое-то хранилище записывается как long, а затем читается как long, без каких-либо промежуточных записей типа long, оно может заменить более позднее чтение со значением, записанным как тип long, даже если были промежуточные записи типа long long.

Отключение псевдонимов на основе типов - это, конечно, один из подходов к созданию такая работа кода. Любой достойный компилятор должен это допускать, и он избежит многих другие возможные ловушки. Тем не менее, похоже, что должен быть стандарт- определенный способ эффективного выполнения такой задачи. Есть?

Ответ 1

существует ли какой-либо эффективный стандартно-совместимый способ разрешить всем трем функциям использовать один и тот же буфер, не требуя, чтобы все данные копировались между вызовами функций? Я сомневаюсь, что авторы правил сглаживания C предполагали, что такая вещь должна быть сложной, но для современных компиляторов модно считать, что ничего, написанное через long *, не будет считаться длинным *, даже если эти типы имеют одинаковое представление.

C указывает, что long и long long являются разными типами, даже если они имеют одинаковое представление. Независимо от представления, они не являются "совместимыми типами" в смысле, определенном стандартом. Поэтому применяется правило строгого псевдонима (C2011 6.5/7): объект, имеющий эффективный тип long, не должен иметь сохраненное значение, доступное по l-значению типа long long, и наоборот. Поэтому, независимо от того, какой тип вашего буфера эффективен, ваша программа демонстрирует поведение undefined, если оно обращается к элементам как типа long, так и типа long long.

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

Тем не менее, похоже, должен существовать стандартный способ эффективного выполнения такой задачи. Есть?

Нет. Эффективным типом буфера является его объявленный тип, если он имеет один или иным образом определяется способом, в котором было установлено его сохраненное значение. В последнем случае это может измениться, если записано другое значение, но любое заданное значение имеет только один эффективный тип. Каким бы ни был его эффективный тип, строгое правило псевдонимов не позволяет получить доступ к значению через lvalues ​​как типа long, так и типа long long. Период.

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

В самом деле, этот или какой-то другой подход, специфичный для реализации, возможно, включая Just Just Works, - это ваши единственные альтернативы для совместного использования одних и тех же данных между тремя функциями, которые вы представляете без копирования.

Обновление:

При некоторых ограниченных обстоятельствах может быть несколько более стандартное решение. Например, с определенными функциями API, которые вы указали, вы можете сделать что-то вроде этого:

union buffer {
    long       l[BUFFER_SIZE];
    long long ll[BUFFER_SIZE];
    int64_t  i64[BUFFER_SIZE]; 
} my_buffer;

fill_array(my_buffer.l, BUFFER_SIZE);
munge_array(my_buffer.i64, BUFFER_SIZE);
output_array(my_buffer.ll, BUFFER_SIZE);

(Реквизит @Riley для того, чтобы дать мне эту идею, хотя она немного отличается от его.)

Конечно, это не работает, если ваш API динамически выделяет сам буфер. Обратите внимание, что

  • Программа, использующая этот подход, может соответствовать стандарту, но если она принимает такое же представление для long, long long и int64_t, то она по-прежнему строго не соответствует, поскольку стандарт определяет, что термин.

  • Стандарт немного непоследователен в этом вопросе. Его замечания о разрешении типа punning через a union приведены в сноске, а сноски являются ненормативными. Переосмысление, описанное в этой сноске, похоже, противоречит пункту 6.5/7, который является нормативным. Я предпочитаю, чтобы мой критически важный код находился далеко от неопределенностей, таких как это, даже если мы пришли к выводу, что этот подход должен работать, неопределенность дает только то, что связано с ошибками компилятора.

  • Довольно известная фигура в поле как-то говорила об этом:

Союзы не полезны [для псевдонимов], независимо от того, что говорят глупые юристы языка, поскольку они не являются общий метод. Профсоюзы работают только для тривиальных и в основном неинтересных случаев, и не имеет значения, что C99 говорит о проблеме, поскольку неприятная вещь, называемая "реальной жизнью", вмешивается.

Ответ 2

Вы можете попробовать сделать это с помощью макросов. Оператор sizeof недоступен препроцессору C, но вы можете сравнить INT_MAX:

#include <limits.h>

#if UINT_MAX == USHRT_MAX
#  define INT_BUFFER ((unsigned*)short_buffer)
#elif UINT_MAX == ULONG_MAX
#  define INT_BUFFER ((unsigned*)long_buffer)
#elif UINT_MAX == ULLONG_MAX
#  define INT_BUFFER ((unsigned*)long_long_buffer)
#else /* Fallback. */
  extern unsigned int_buffer[BUFFER_SIZE];
#  define INT_BUFFER int_buffer
#endif

Это вопрос C, но в С++ вы можете сделать это более увлекательным способом с помощью специализации шаблонов и шаблонов шаблонов типов.