Данные фиксированной длины в C/С++

Я слышал, что размер типов данных, таких как int, может различаться для разных платформ.

Мой первый вопрос: может ли кто-нибудь привести пример, что пойдет не так, когда программа предполагает, что int - 4 байта, но на другой платформе это 2 байта?

Другой вопрос, который у меня был, связан. Я знаю, что люди решают эту проблему с помощью некоторых typedefs, например, у вас есть переменные, такие как u8, u16, u32 - которые гарантированно будут 8 бит, 16 бит, 32 бит, независимо от платформы - мой вопрос в том, как это достигается обычно? (Я не имею в виду типы из библиотеки stdint - мне любопытно вручную, как можно обеспечить, чтобы какой-то тип всегда говорил 32 бита независимо от платформы?)

Ответ 1

Я знаю, что люди решают эту проблему с помощью некоторых typedef, например, у вас есть такие переменные, как u8, u16, u32, гарантированные 8 бит, 16 бит, 32 бита, независимо от платформы

Существуют некоторые платформы, у которых нет типов определенного размера (например, TI 28xxx, где размер char равен 16 бит). В таких случаях невозможно иметь 8-битный тип (если вы этого не хотите, но это может привести к поражению производительности).

как это достигается обычно?

Обычно с typedefs. c99 (и С++ 11) имеют эти typedefs в заголовке. Поэтому просто используйте их.

может ли кто-нибудь привести пример, что пойдет не так, когда программа предполагает, что int имеет 4 байта, а на другой платформе - 2 байта?

Лучшим примером является связь между системами с разным размером. Отправляя массив ints с одной на другую платформу, где sizeof (int) отличается на два, нужно проявлять особую осторожность.

Также, сохраняя массив ints в двоичном файле на 32-битной платформе и переинтерпретируя его на 64-битной платформе.

Ответ 2

В более ранних итерациях стандарта C вы обычно делали свои собственные операторы typedef, чтобы убедиться, что вы получили 16-разрядный тип (например) на основе строк #define, переданных в компилятор, например:

gcc -DINT16_IS_LONG ...

В настоящее время (C99 и выше) существуют определенные типы, такие как uint16_t, точное целое число без знака 16 бит.

Если вы включите stdint.h, вы получите точные типы ширины бит, типы с наименьшей шириной, самые быстрые типы с заданной минимальной шириной и так далее, как описано в C99 7.18 Integer types <stdint.h>. Если реализация имеет совместимые типы, они должны предоставить их.

Также очень полезно inttypes.h, которое добавляет некоторые другие опрятные функции для преобразования формата этих новых типов (строки printf и scanf).

Ответ 3

Для первого вопроса: Переполнение целых чисел.

Для второго вопроса: например, к typedef целое число без знака 32 бита, на платформе, где int - 4 байта, используйте:

 typedef unsigned int u32;

На платформе, где int - 2 байта, а long - 4 байта:

typedef unsigned long u32;

Таким образом, вам нужно только изменить один заголовочный файл, чтобы сделать типы кросс-платформенными.

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

#if defined(PLAT1)
typedef unsigned int u32;
#elif defined(PLAT2)
typedef unsigned long u32;
#endif

Если поддерживается C99 stdint.h, он предпочитает.

Ответ 4

Прежде всего: никогда не пишите программы, которые полагаются на ширину таких типов, как short, int, unsigned int,....

В принципе: "никогда не полагайтесь на ширину, если она не гарантируется стандартом".

Если вы хотите быть независимым от платформы и хранить, например, значение 33000 как целое число со знаком, вы не можете просто предположить, что это будет int. А int имеет, по крайней мере, диапазон от -32767 до 32767 или -32768 до 32767 (в зависимости от одного/двухкомпонентного дополнения). Этого недостаточно, хотя обычно это 32 бита и поэтому способно хранить 33000. Для этого значения вам определенно нужен тип >16bit, поэтому вы просто выбираете int32_t или int64_t. Если этого типа не существует, компилятор скажет вам об ошибке, но это не будет бесшумной ошибкой.

Во-вторых: С++ 11 предоставляет стандартный заголовок для целых чисел фиксированной ширины. Ни один из них не гарантированно существует на вашей платформе, но когда они существуют, они гарантированно имеют точную ширину. См. эту статью на cppreference.com для справки. Типы называются в формате int[n]_t и uint[n]_t, где n есть 8, 16, 32 или 64. Вам нужно будет включить заголовок <cstdint>. Заголовок C, конечно, <stdint.h>.

Ответ 5

обычно проблема возникает, когда вы максимизируете число или когда вы сериализуете. Менее распространенный сценарий возникает, когда кто-то делает предположение о явном размере.

В первом сценарии:

int x = 32000;
int y = 32000;
int z = x+y;        // can cause overflow for 2 bytes, but not 4

Во втором сценарии

struct header {
int magic;
int w;
int h;
};

то один переходит в fwrite:

header h;
// fill in h
fwrite(&h, sizeof(h), 1, fp);

// this is all fine and good until one freads from an architecture with a different int size

В третьем сценарии:

int* x = new int[100];
char* buff = (char*)x;


// now try to change the 3rd element of x via buff assuming int size of 2
*((int*)(buff+2*2)) = 100;

// (of course, it easy to fix this with sizeof(int))

Если вы используете относительно новый компилятор, я бы использовал uint8_t, int8_t и т.д., чтобы быть уверенным в размере типа.

В старых компиляторах typedef обычно определяется для каждой платформы. Например, можно сделать:

 #ifdef _WIN32
      typedef unsigned char uint8_t;
      typedef unsigned short uint16_t;
      // and so on...
 #endif

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

Ответ 6

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

Если вы хотите, чтобы ваша (современная) компиляция программы на С++ завершилась неудачей, если данный тип не соответствует той ширине, которую вы ожидаете, добавьте static_assert где-нибудь. Я бы добавил это вокруг, где делаются предположения о ширине типа.

static_assert(sizeof(int) == 4, "Expected int to be four chars wide but it was not.");

chars на большинстве используемых платформ 8 бит, но не все платформы работают таким образом.

Ответ 7

Ну, первый пример - что-то вроде этого:

int a = 45000; // both a and b 
int b = 40000; // does not fit in 2 bytes.
int c = a + b; // overflows on 16bits, but not on 32bits

Если вы посмотрите в заголовок cstdint, вы увидите, как определяются все типы фиксированного размера (int8_t, uint8_t и т.д.) - и только одно отличие между разными архитектурами - это заголовочный файл. Таким образом, по одной архитектуре int16_t может быть:

 typedef int int16_t;

а другой:

 typedef short int16_t;

Кроме того, существуют и другие типы, которые могут быть полезны, например: int_least16_t

Ответ 8

  • Если тип меньше, чем вы думаете, возможно, он не сможет сохранить значение, которое вам нужно сохранить в нем.
  • Чтобы создать типы фиксированного размера, вы читаете документацию для поддерживаемых платформ, а затем определяете typedef на основе #ifdef для определенных платформ.

Ответ 9

может ли кто-нибудь привести пример, что пойдет не так, когда программа предполагает, что int имеет 4 байта, а на другой платформе - 2 байта?

Предположим, вы разработали программу для чтения 100 000 входов, и подсчитываете ее, используя unsigned int, предполагая размер 32 бит (32-разрядные беззнаковые числа могут считать до 4 294 967 295). Если вы скомпилируете код на платформе (или компиляторе) с 16-битными целыми числами (16-разрядные беззнаковые ints могут рассчитывать только до 65535), то значение обернется вокруг 65535 из-за емкости и обозначит неправильный счет.

Ответ 10

Компиляторы несут ответственность за соблюдение стандарта. Когда вы включаете <cstdint> или <stdint.h>, они должны предоставлять типы в соответствии со стандартным размером.

Компиляторы знают, что они компилируют код для какой платформы, тогда они могут генерировать некоторые внутренние макросы или магии для создания подходящего типа. Например, компилятор на 32-битной машине генерирует макрос __32BIT__, и ранее он имеет эти строки в заголовочном файле stdint:

#ifdef __32BIT__
typedef __int32_internal__ int32_t;
typedef __int64_internal__ int64_t;
...
#endif

и вы можете использовать его.

Ответ 11

битовые флаги являются тривиальным примером. 0x10000 вызовет проблемы, вы не сможете замаскировать его или проверить, установлен ли бит в этой 17-й позиции, если все усечено или разбито, чтобы вписаться в 16 бит.