Лучшая практика совместимости с 32-разрядными системами для программного обеспечения, использующего файлы данных, зависящие от версии?

#c #c #design-patterns #compatibility #32bit-64bit

Вопрос:

В простом случае, когда программа независима от какой-либо внешней базы данных, можно было бы написать что-то вроде:

 #include <stdint.h>
#if UINTPTR_MAX == 0xffffffff
/* 32-bit */
typedef some_type_for_32_bit_version Type;

#elif UINTPTR_MAX == 0xffffffffffffffff
/* 64-bit */
typedef some_type_for_64_bit_version Type;
#endif
 

И продолжайте работать с нужным типом.

Теперь предположим, что программа сначала считывает некоторый файл метаданных/данных, некоторый бит представляет, создан ли файл для 32-разрядной или 64-разрядной версии (файлы, созданные для 32-разрядной версии, также должны работать на 64-разрядной версии, файлы, созданные для 64-разрядной версии, должны работать только на 64-разрядной версии). Программа может работать практически одинаково в обоих случаях, но имеет небольшие различия, например, некоторая переменная uint32_t для 32-разрядной и uint64_t для 64-разрядной.

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

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

Есть ли для этого какой-то общий план? Что-то особенное для c? c ?

Комментарии:

1. вы хотите сказать, что файл сохранения может быть 32 или 64-разрядным (без какого-либо заголовка)? как бы вы обрабатывали 64-разрядные данные в 32-разрядной программе? если вы можете, то в чем проблема?

2. @appleapple Это может быть файл, созданный программным обеспечением для использования на 32-разрядных машинах (который также должен работать на 64-разрядных машинах), или файл, созданный для 64-разрядных машин (который должен работать только для 64-разрядных), отредактировал вопрос

3. Решение известно как сериализация . Идея заключается в том, что 64-разрядная система имеет 64-разрядную структуру, совместимую с обоими форматами файлов. 32-разрядная система имеет структуру, совместимую только с 32-разрядным форматом файла. При чтении 32-разрядного файла в 64-разрядной системе код десериализации преобразует 32-разрядные данные в 64-разрядные. После того, как 64-разрядная структура заполнена 32-разрядными данными, остальную часть кода не должно волновать, что данные получены из 32-разрядного файла.

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

5. Лучшим решением было бы для начала не иметь отдельных 32-битных и 64-битных версий файла данных. Используйте один формат файла, совместимый как с 32-битными, так и с 64-битными программами, например, сохраняя в файле только 64-битные данные.

Ответ №1:

Есть две вещи, которые вам нужны:

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

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

  • Динамический выбор вариантов во время выполнения. Концепция, которая может сделать более сложные настройки более управляемыми, — это динамическая отправка. Там вы создадите универсальный интерфейс и создадите экземпляр 32-или 64-разрядного варианта, зависящего от типа.

    В C это был бы класс интерфейса с виртуальными методами. В C это можно легко воспроизвести как структуру, содержащую указатели на функции.

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

Основываясь на вашем описании, я бы предложил что-то простое, например, следующее:

 // When reading the file:
bool flagIs64bit = readThatFlag();
if (currentArchIs32bit() amp;amp; flagIs64bit) {
  return NotSupported;
}

// At the core of the parser:
uintptr_t readType(void *p) {
  if (flagIs64bit) {
    return (uintptr_t)*(uint64_t*)p;
  } else {
    return (uintptr_t)*(uint32_t*)p;
  }
}
 

(Комментарий к дизайну: Двоичные файлы, как правило, должны быть независимы от архитектуры, в которой они используются. Таким образом, они совместимы с как можно большим количеством систем. Это означает, что следует использовать только явные типы ширины (u)intNN_t , и их значение, возможно, потребуется преобразовать.)