【问题标题】:fixed length data types in C/C++C/C++ 中的固定长度数据类型
【发布时间】:2018-10-12 02:09:31
【问题描述】:

我听说int 等数据类型的大小可能因平台而异。

我的第一个问题是:有人可以举一些例子吗,出了什么问题,当程序 假设 int 是 4 个字节,但在不同的平台上是 2 个字节?

我的另一个问题是相关的。我知道人们用一些typedefs 来解决这个问题, 就像你有像u8,u16,u32 这样的变量 - 无论平台如何,它们都保证是 8 位、16 位、32 位 - 我的问题是,这通常是如何实现的? (我不是指来自stdint 库的类型 - 我很好奇手动,如何强制某些类型总是说 32 位,而不管平台如何?)

【问题讨论】:

  • 覆盖内存存在潜在问题。如果您假设一个整数在另一个平台上是 2 个字节时是 4 个字节,这取决于内存的布局方式,您可以覆盖整数之后的接下来的 2 个字节。
  • 现在是阅读 Usenet 时代的(旧的,但信息量很大的)C 常见问题解答的好时机:faqs.org/faqs/C-faq/abridged 然后faqs.org/faqs/C-faq/faq(未删节,所以如果你可以阅读那个代替!更多信息)。它讨论了其中的许多,以及许多其他经常错误的假设(NULL 的内部表示等)。 (必读的是关于 null 和指针/数组的章节。其余的也很好,对许多主题大开眼界)
  • 请注意字节顺序也可能因平台而异。 (+1 提问 - 最好先提问,然后假设“sizeof(void *) 肯定永远是 4)。
  • @MaciejPiechotka:同意。发布这些内容很好,因为许多读者可能会意识到潜在的陷阱及其解决方案!没有不好的问题[好吧,如果他们提供足够的上下文],只有不好的答案^^

标签: c++ c


【解决方案1】:

我知道人们用一些 typedef 来解决这个问题,比如你有像 u8、u16、u32 这样的变量——无论平台如何,它们都保证是 8 位、16 位、32 位

有些平台没有特定大小的类型(例如 TI 的 28xxx,其中 char 的大小为 16 位)。在这种情况下,不可能有 8 位类型(除非你真的想要它,但这可能会影响性能)。

这通常是如何实现的?

通常使用 typedef。 c99(和 c++11)有these typedefs in a header。所以,就用它们吧。

谁能举个例子,当程序假设一个 int 是 4 个字节,但在不同的平台上它是 2 个字节时,出了什么问题?

最好的例子是不同字体大小的系统之间的通信。将整数数组从一个平台发送到另一个平台,其中两个平台的 sizeof(int) 不同,必须格外小心。

此外,在 32 位平台上将整数数组保存在二进制文件中,并在 64 位平台上重新解释它。

【讨论】:

  • +1 用于在 32 位平台上将整数数组保存在二进制文件中,并在 64 位平台上重新解释它。.
【解决方案2】:

在 C 标准的早期迭代中,您通常根据传递给编译器的 #define 字符串创建自己的 typedef 语句以确保获得(例如)16 位类型:

gcc -DINT16_IS_LONG ...

如今(C99 及更高版本),有一些特定类型,例如 uint16_t,即 16 位宽的无符号整数。

如果您包含stdint.h,您将获得精确的位宽类型、至少该宽度的类型、具有给定最小宽度的最快类型等等,如C99 7.18 Integer types <stdint.h> 中所述。如果实现具有兼容的类型,则它们必须提供这些类型。

inttypes.h 也非常有用,它为这些新类型(printfscanf 格式字符串)的格式转换添加了一些其他简洁的功能。

【讨论】:

  • 子问题:如果平台不支持16位整数类型是unint16_t未定义在cstdint等..?或者标准是否保证类型永远存在(并在内部做一些事情以确保它工作)?
  • 不,C 标准只有在实现具有兼容类型时才需要它。例如,如果您在 12 位 DSP 上运行,那么它就没有 来提供 16 位 uint16_t。 可能,但这不是强制性的:7.18.1.1/3: These types are optional. However, if an implementation provides integer types with widths of 8, 16, 32, or 64 bits, no padding bits, and (for the signed types) that have a two’s complement representation, it shall define the corresponding typedef names.
  • 因此,如果您使用uint16_t 并且平台不支持它,那么我们可以预期在移植过程中会出现编译错误。
  • @Loki,是的,编译器不会知道类型。
【解决方案3】:

第一个问题:Integer Overflow

对于第二个问题:例如,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,则首选。

【讨论】:

  • 没关系,有这样的时候...... - 休息一下!
  • 这里的平台是什么?是硬件 - 如 x86、x86_64、AMD 等...还是操作系统 - 如 Solaris、AIX、HP-UX、Linux、macOS、BSD 和 IBM z/OS 等......?
【解决方案4】:

首先:永远不要编写依赖于类型宽度的程序,例如shortintunsigned int、....

基本上:“永远不要依赖宽度,如果标准没有保证的话”。

如果您想真正独立于平台并存储例如值 33000 作为有符号整数,您不能只是假设 int 将持有它。 int 至少具有-3276732767-3276832767 的范围(取决于一个/两个补码)。这还不够,尽管它通常是 32 位,因此能够存储 33000。对于这个值,您肯定需要 >16bit 类型,因此您只需选择 int32_tint64_t。如果该类型不存在,编译器会告诉你错误,但不会是无声错误。

第二:C++11 为固定宽度整数类型提供了标准头文件。这些都不能保证在您的平台上存在,但是当它们存在时,它们保证具有精确的宽度。请参阅this article on cppreference.com 以获取参考。这些类型以int[n]_tuint[n]_t 的格式命名,其中n8163264。您需要包含标题<cstdint>C 标头当然是 <stdint.h>

【讨论】:

  • OP: "我不是指 stdint 库中的类型 - 我很好奇手动,如何强制某些类型总是说 32 位而不考虑平台?) i>";
  • @legends2k 拥有固定宽度整数类型的正确方法使用标准库。
  • 同意,但那是您编写代码的时候,而不是您尝试了解如何首先编写此类标头的时候。
  • "首先:永远不要编写依赖类型宽度的程序。" 所以你是说我们不应该依赖uint32_t 是32 位宽?抽象很好,但最终会出现一个点,即您需要做出一些假设才能真正完成任何事情。
  • “永远不要编写依赖于类型宽度的程序”是什么意思?类型的宽度直接影响可能值的范围,这在选择使用什么类型时非常重要,尤其是对于许多人使用 C/C++ 进行的那种编程任务。如果你正在编写一个文件系统或任何需要在受限内存中存储大量值的东西,你需要做出这些决定。字符串不存储为 unsigned long long 数组是有原因的。
【解决方案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's 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

这样,每个平台都会有一个标头来定义该平台的细节。

【讨论】:

  • +1 是第一个提到结构的人。您还应该注意通过网络发送 strct 时会发生什么。
【解决方案6】:

我很好奇手动,如何强制某种类型总是说 32 位,而不管平台如何?

如果您希望(现代)C++ 程序的编译在给定类型不是您期望的宽度时失败,请在某处添加static_assert。我会在关于类型宽度的假设周围添加这个。

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

chars 在最常用的平台上为 8 位大,但并非所有平台都以这种方式工作。

【讨论】:

  • sizeof 实际上以chars 为单位返回大小,而不是字节。所以如果你想检查 bits 的大小,你应该做sizeof(int) * CHAR_BIT == 32.
  • static_assert 仅在最新标准中可用。但是 uint_32t 和之前的类似类型都可以使用
  • @user694733 否。根据定义,以字符为单位的大小 = 以字节为单位的大小。 sizeof(char)==1 – 总是。
  • @sammy Nope、uint32_t 等与static_assert 同时添加。
  • @KonradRudolph 这取决于字节的定义。字节通常被认为是 8 位。 char 总是有 CHAR_BIT 位。 CHAR_BIT 至少为 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_tuint8_t 等)是如何定义的——不同架构之间唯一不同的是这个头文件。所以,在一个架构上int16_t可能是:

 typedef int int16_t;

还有一个:

 typedef short int16_t;

此外,还有其他可能有用的类型,例如:int_least16_t

【讨论】:

    【解决方案8】:
    1. 如果一个类型比您想象的要小,那么它可能无法存储您需要在其中存储的值。
    2. 要创建固定大小的类型,请阅读要支持的平台的文档,然后根据 #ifdef 为特定平台定义 typedefs。

    【讨论】:

      【解决方案9】:

      谁能举个例子,当程序假设一个 int 是 4 个字节,但在不同的平台上它是 2 个字节时,出了什么问题?

      假设您已将程序设计为读取 100,000 个输入,并且您使用 unsigned int 对其进行计数,假设大小为 32 位(32 位无符号整数可以计数到 4,294,967,295)。如果您在使用 16 位整数(16 位无符号整数只能计数到 65,535)的平台(或编译器)上编译代码,则由于容量的原因,该值将回绕超过 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 位。

          【讨论】:

            猜你喜欢
            • 2012-11-12
            • 1970-01-01
            • 1970-01-01
            • 2019-03-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-08-30
            相关资源
            最近更新 更多