【问题标题】:Windows Data Types... why so redundant/undescriptive?Windows 数据类型...为什么如此冗余/不具描述性?
【发布时间】:2011-07-05 21:29:54
【问题描述】:

有人能问一下为什么定义了以下typedefs/#defines吗?与原版相比,它们有什么价值?

typedef char CHAR;
#define CONST const
typedef float FLOAT;

typedef unsigned __int64 DWORD64; //A 64-bit "double"-word?!
typedef ULONGLONG DWORDLONG;      //What's the difference?

typedef ULONG_PTR DWORD_PTR;      //What's the difference?
typedef long LONG_PTR;            //Wasn't INT_PTR enough?

typedef signed int LONG32;        //Why not "signed long"?
typedef unsigned int UINT;        //Wait.. UINT is "int", "LONG" is also int?
typedef unsigned long ULONG;      //ULONG is "long", but LONG32 is "int"? what?

typedef void *PVOID;              //Why not just say void*?
typedef void *LPVOID;             //What?!

typedef ULONG_PTR SIZE_T;         //Why not just size_t?

而且,最重要的是:

#define VOID void                 //Assuming this is useful (?), why not typedef?

这些背后的原因是什么?是不是我不理解的某种抽象?


编辑

对于那些提到编译器交叉兼容性的人:

我的问题不是关于他们为什么不使用unsigned long long 而不是DWORD64。我的问题是为什么有人会使用DWORD64 而不是ULONG64(反之亦然)? typedefed 不是都是 64 位宽吗?

或者,作为另一个例子:即使在一个旨在在各个方面欺骗我们的“假设”编译器中,ULONG_PTRUINT_PTRDWORD_PTR 之间有什么区别?这些所有抽象数据类型不都是同一个意思吗——SIZE_T

但是,我am问他们为什么使用ULONGLONG 而不是long long——在含义上是否存在任何潜在差异,long longDWORDLONG 都没有涵盖?

【问题讨论】:

  • 我怀疑其中有很多来自 win16 的黑暗时代,但我会把它留给那些真正在过去编写 windows 代码的人来回答:)
  • 影响这些类型历史的因素有很多。一旦定义,就永远无法收回。 #define VOID void 已完成,因为最初 void* 最初不在 C 标准中(找不到链接...)。所以一些编译器没有识别它。但是如果你现在删除它,你就会破坏代码。当更新 SDK 时,没有人希望他的代码中断。更新的时候已经有太多问题了,为什么要在一些“世纪”未触及但现在突然不再编译的旧模块中打破它。
  • @Christopher:哦,关于void 的有趣案例。其他的呢,比如char?是否有任何时候char 也未定义?
  • DWORD64 与 UINT64 的冗余定义是由于微软的几个团队定义了自己的类型。也许内核团队喜欢 DWORD64,然后 GDI 团队来定义 UINT64。多年后,在整合 SDK 标头时,SDK 团队试图摆脱多个定义并将它们移至同一个基本标头。此团队无法删除定义。所以我们现在有这个“混乱”。 (这有点猜测..)

标签: c winapi types history


【解决方案1】:

大部分冗余名称的存在主要有两个原因:

  • 它们是为向后兼容而保留的历史类型
  • 它们是来自不同开发团队的同一类型的不同名称(对于像 Windows 这样庞大的项目,团队很难保持一致)

typedef char CHAR;

char 的签名可能因平台和编译器而异,这是原因之一。最初的开发者可能也对未来字符编码的变化保持开放态度,但当然这不再相关,因为我们现在使用 TCHAR 来实现这一目的。


typedef unsigned __int64 DWORD64; //A 64-bit "double"-word?!

在迁移到 64 位期间,他们可能发现他们的一些 DWORD 参数确实需要为 64 位长,他们可能将其重命名为 DWORD64,以便这些 API 的现有用户不会感到困惑。


typedef void *PVOID;              //Why not just say void*?
typedef void *LPVOID;             //What?!

这可以追溯到 16 位时代,当时有常规的 16 位“近”指针和 32 位“远”指针。类型上的L 前缀代表“long”或“far”,现在这已经没有意义了,但在那个时候,这些可能是这样定义的:

typedef void near *PVOID;
typedef void far *LPVOID;

更新: 至于FLOATUINTULONG,这些只是“越抽象越好”的例子,考虑到未来的变化。请记住,Windows 也可以在 x86 以外的平台上运行——您可以考虑一种架构,其中浮点数以非标准格式表示,并且 API 函数经过优化以利用这种表示。这可能与 C 的 float 数据类型发生冲突。

【讨论】:

  • +1 很好的解释! FLOATUINTULONG 呢?为什么不直接使用floatunsigned intunsigned long? (UINTULONG 是固定大小的吗?)哦,那么 CHAR 是签名还是未签名?
  • @Mehrdad:查看我的更新。现在你问它,我不太确定CHAR 的签名——我的回答充满了猜测,但这是我能做的最好的。
  • @casablanca:呃...INTint“更抽象”怎么样?而且,您能否举个例子说明引入FLOAT可能如何跨平台和/或编译器有用,即使在远程的理论意义上?
  • @Mehrdad:因为FLOATINT 等定义的类型代表了Windows API 所期望的类型,这可能与您的编译器对floatint 的解释不同.请记住,API 代码已经编译到操作系统附带的 DLL 中,因此您编写的任何代码都需要与其二进制兼容。
  • @casablanca:嗯……你的意思是说它们很有用,例如您使用带有 little-endian 系统的 big-endian 编译器? (我想不出为什么有人会这样做,但我想这在理论上是可能的......)
【解决方案2】:

25 年前首次构建 Windows API 头文件时,int 为 16 位,long 为 32 位。头文件随着时间的推移而演变,以反映编译器和硬件的变化。

此外,Microsoft C++ 并不是唯一可以处理 Windows 头文件的 C++ 编译器。当 Microsoft 添加 size_t 关键字时,并非所有编译器都支持它。但他们可以轻松地创建一个宏 SIZE_T 来表达它。

此外,有(或曾经有)将 API 头文件从 C/C++ 转换为其他语言的自动化工具。其中许多工具最初是为使用当前(当时)的标头定义而编写的。如果 Microsoft 只是按照您的建议更改头文件以简化它们,那么其中许多工具将停止工作。

基本上,头文件将 Windows 类型映射到最小公分母,以便多个工具可以使用它们。有时看起来确实有些混乱,我怀疑如果微软愿意放弃任何向后兼容的表象,他们可以减少大部分混乱。但是这样做会破坏很多工具(更不用说很多文档了)。

所以,是的,Windows 头文件有时是一团糟。这就是我们为进化、向后兼容性和使用多种语言的能力付出的代价。

附加信息:

我同意乍一看所有这些定义似乎很疯狂。但是作为一个看到 Windows 头文件随时间演变的人,我理解它们是如何产生的。这些定义中的大多数在引入时都非常有意义,即使现在它们看起来很疯狂。至于具体情况ULONGLONGDWORD64,我想它们是为了一致性而添加的,因为旧的头文件有ULONGDWORD,所以程序员会期待另外两个。至于为什么ULONGDWORD在同一个东西的时候都被定义了,我可以想到几种可能,其中两种是:

  • 一个 API 团队使用 ULONG,另一个使用 DWORD,当合并头文件时,他们只是保留两者,而不是通过转换为一个或另一个来破坏代码。
  • 有些程序员更愿意考虑ULONG 而不是DWORDULONG 表示您可以对其进行数学运算的整数类型,而 DWORD 仅表示某种通用的 32 位值,通常是您不想修改的键、句柄或其他值.

您最初的问题是,看似疯狂的定义背后是否有某种推理,或者您是否没有遗漏抽象。简单的答案是定义在演变,当时的变化是有意义的。没有特别的抽象,但目的是如果您编写代码以使用标头中定义的类型,那么您应该能够将您的代码从 32 位移植到 64 位没有麻烦。也就是说,DWORD 在两种环境中都是相同的。但是如果你在 API 说返回值为HANDLE 时使用DWORD 作为返回值,你就会遇到麻烦。

【讨论】:

  • 感谢您的回复,但请查看我的编辑。我的问题不是关于他们为什么不使用内置数据类型(这显然因编译器而异......除了void,这让我感到困惑),而是为什么他们对同一个抽象数据类型有多个名称,例如DWORD64ULONG64 表示“64 位宽度的整数”。为什么客户会选择ULONG_PTR而不是UINT_PTR,或DWORDLONG而不是ULONGLONGDWORD64ULONG64
  • @ Mehrdad - 因为标准中没有说明 long long 有多大(除了它必须至少与 int 一样大)。 DWORD64 在从 16 位窗口到 128 位 Cray 的任何东西上都是 64 位
  • @Marting:等等,那么ULONG64ULONGLONG 有什么区别?如果ULONG64 始终是 64 位,那么为什么不使用DWORD64?如果它总是 64 位,那为什么不使用ULONGLONG
  • 嗯...这并没有解释所有类型,但它仍然很有启发性,+1。
【解决方案3】:

一个原因是为了在 C 编译器之间保持某种可移植性。

尤其是DWORD64,理论上你只需要改变DWORD64的定义就可以让代码在其他编译器上编译。

【讨论】:

  • 为什么不到处使用ULONG64?剩下的呢,比如VOID? (我问的是定义中的冗余类型定义的数量......即使在理论上,是否有任何理由使用DWORD64而不是ULONG64?)跨度>
  • 不同的编译器对 64 位整数有不同的“内置”名称。不过 VOID typedef 似乎有点毫无意义 - 无法解释那个。
  • @Jimmy:我的问题不在于他们为什么不使用unsigned long long;我的问题是为什么有人会使用DWORD64 而不是ULONG64(反之亦然)? typedefed both 不是都是 64 位吗?
  • unsigned long long;因为MS没有这种类型。他们在 C89 停了下来,后来 unsigned long 来了。零件是在考虑很久之前就引入的,所以我们有 LARG_INTERGER 之类的
  • @Friedrich:我不确定你是否在回答我的最后一条评论,但这并没有回答我为什么我们使用DWORD64 而不是ULONG64 的问题。 (不过,其他人回答了。)
猜你喜欢
  • 2018-09-19
  • 2013-01-27
  • 1970-01-01
  • 1970-01-01
  • 2020-02-07
  • 1970-01-01
  • 1970-01-01
  • 2013-09-18
  • 1970-01-01
相关资源
最近更新 更多