uintptr_t uint_to_uintptr(unsigned int n) {
    return (uintptr_t)n;
}

uintptr_t ulint_to_uintptr(unsigned long int n) {
    return (uintptr_t)n;
}

uintptr_t ullint_to_uintptr(unsigned long long int n) {
    return (uintptr_t)n;
}

上述 3 种转换中的哪一种不会导致信息(位)丢失?

c99标准规定:

以下类型指定一个无符号整数类型,其属性是任何指向 的有效指针void都可以转换为此类型,然后转换回指向 的指针void,结果将与原始指针相等:

uintptr_t

但它没有具体说明它的大小unsigned integer type

它是否意味着最大宽度无符号整数类型的最大值(UINTMAX_MAX)或简单的unsigned int

18

  • 4
    如果已知某个标准整数类型足够大,则不需要此类型。为什么需要这样做?


    – 

  • 3
    实现可能有 64 位long long但只有 32 位指针。例如较旧的 32 位 MS VC,其中sizeof(uintptr_t)4。所以不,您不一定能做到这一点。


    – 


  • 2
    uintptr_t唯一明确定义的事情是在和之间来回转换void*。为什么需要将常规无符号整数转换为uintptr_t


    – 

  • 1
    您的引文中提到了无符号整数类型。您忽略了转换为此类型的是指针uintptr_t这一事实。


    – 


  • 2
    您不能使用它来代替。当您出于某种原因需要将指针转换为整数时,void*可以用它来代替。unsigned long long


    – 


最佳答案
4

前段时间,我调查了所有可以找到编译器的半新平台。只有 6 种整数/指针大小“模型”(只有指针、int 和 long 在它们之间有所不同;sizeof(size_t) 始终与 sizeof(uintptr_t) 匹配 [编辑:除了 DOS 的“大”模型,也可能是中型和紧凑型];请注意,并非所有指针位都可能被实际使用并且当有多个给定宽度的整数时,不同的 typedef 可能属于不同级别的整数):

  • SP16。指针是 16 位;int 和 long 是 32 位。这个会失败,但这种情况很少见(至少作为默认配置,尽管替代配置通常也是下一个失败的配置)。
  • IP16。指针和 int 是 16 位;long 是 32 位。这个也会失败,而且更常见。
  • LP32。指针和长整型为 32 位;整型仅为 16 位。这包括仅使用远指针时的 16 位 x86(例如 MS-DOS)。
  • ILP32。指针、整数和长整型都是 32 位。这是过去几年之前最常见的平台类型。
  • LP64。Int 是 32 位;long 和指针是 64 位。这是目前最常见的平台类型。
  • LLP64。int 和 long 是 32 位;指针是 64 位。这仅在 Windows 上使用。

4

  • 严格地说:我观察到 LP32 的 DOS 版本有 sizeof(size_t) = 2。


    – 

  • 哦,对了。我实际上没有得到那个编译器,所以我没有把它存储在同一个地方。通过重新阅读文档,你对“大型”模型的说法是正确的,但对“巨型”模型(允许>64KB 数组)的说法不正确。


    – 

  • 而且几乎没有人使用巨型模型,因为指针算法太慢了。相反,你将少数几个单独的指针声明为巨型,并调用 hmalloc 来分配一个大数组。


    – 

  • 2
    “LP64。Int 是 32 位;long 和指针是 64 位。这是目前最常见的平台类型。” –> 鉴于嵌入式平台是最常见的(IIRC,每年数十亿),我怀疑其中最大的份额是 64 位地址方案。


    – 

关于这些类型的宽度1和表示能力以及它们之间的关系,C 2018 标准规定每种类型至少可以表示这里显示的值:

类型 价值 标准来源
unsigned int 65,535 5.2.4.2.1 1
unsigned long int 4,294,967,295 5.2.4.2.1 1
unsigned long long int 18,446,744,073,709,551,615 5.2.4.2.1 1
uintptr_t 65,535 7.20.2.4 1

并规定了以下额外要求:

  • unsigned long int至少与unsigned int(6.3.1.1 1,作为等级属性的结果)一样宽。

  • unsigned long long int至少与unsigned int和一样宽unsigned long long int(6.3.1.1 1,作为等级属性的结果)。

  • uintptr_t可以以某种方式表示任何有效的void *(7.20.1.4 1)。

从这些中我们可以看出,类型 、 或 的大小没有限制unsigned intunsigned long intunsigned long long int没有规则阻止它们中的任何一个比 更宽或更窄uintptr_t。因此,C 标准无法保证将unsigned intunsigned long int或转换unsigned long long intuintptr_t不会丢失信息。

脚注

1整数类型的宽度是用于表示其值的位数,包括符号位(如果有)。精度用于表示其值的位数(不包括任何符号位)。对于无符号类型,宽度和精度相同。

2

  • 感谢您的回复,但我的问题恰恰相反,我可以将无符号整数转换为而uintptr_t不会丢失位吗?相反,您回答了这个问题:我可以将转换uintptr_t为无符号整数而不会丢失位吗?


    – 

  • 2
    @ParminderSingh:已修复。结论是一样的;C 标准没有规定uintptr_t和其他无符号整数类型的宽度之间的任何关系,因此任一方向的转换都可能丢失信息。


    – 

short关键字表示数据类型的long不同宽度。

所谓“宽度”,具体是指数据类型的位宽。

C99 标准允许其中一些类型“表示相同类型”,但定义了UINT_MAX指定保证最小值的宏(例如),并且每个保证最小值都对应于特定的宽度。

例如,标准要求UINT_MAX至少为65535,这意味着标准要求unsigned int至少为16位长。

同样,C99 标准要求unsigned long int长度至少为 32 位,并且unsigned long long int至少为 64 位。

C99 标准定义uintptr_t并要求其宽度足够容纳unsigned intvoid*,以较大者为准。甚至有一个UINTPTR_MAX宏明确要求uintptr_t其长度至少为 16 位。

标准可能在说uintptr_t“[…] 指定无符号整数类型 […]”时更清楚一些,但按照我的理解,它具体指的是unsigned int,所以它具体不需要足够宽来容纳uintptr_t任何大于该值的整数类型,这意味着你不能指望uintptr_t容纳一个unsigned long int,更不用说一个了unsigned long long int

因此,根据 C99 标准(以及我的解读方式),您的uint_to_uintptr()函数保证可以工作,但您ulint_to_uintptr()和您的ullint_to_uintptr()函数却不能保证可以工作。

(换句话说,恐怕这只是一厢情愿而已。)

4

  • 1
    第一次阅读时,我认为您通过提出 来一针见血UINTPTR_MAX(我甚至接受了您的答案作为解决方案),但现在我产生了怀疑。UINTPTR_MAX宏要求至少uintptr_t16 位长,就像要求至少16 位长一样,但我们必须注意“至少”这个词,这意味着 可以是 32 位长,也可以是16 位长,这完全符合 C 标准。这意味着我提出的任何转换都不能保证没有位丢失。UINT_MAXunsigned longunsigned intuintptr_t


    – 


  • 1
    @ParminderSingh非常正确。但公平地说,我预计UINT_MAX > UINTPTR_MAX这只适用于低内存、高计算能力的处理器。由于我不记得见过符合这一要求的处理器(几十年来),所以答案实际上是正确的。添加一个static_assert(UINT_MAX <= UINTPTR_MAX)以捕获奇怪的编译器。


    – 


  • @chux-ReinstateMonica 我也期望UINT_MAX > UINTPTR_MAX在现实世界中始终如此,但这更多的是对 C 标准的好奇。


    – 

  • @ParminderSingh我也期望UINT_MAX > UINTPTR_MAX它永远是真的… 我认为你写反了。


    – 

我可以投一个unsigned long intuintptr_t

[OP 改变了一些东西]

我可以将一个无符号长整型转换为 uintptr_t 而不丢失位吗?

唯一保证不会丢失信息的无符号类型是从unsigned charuintptr_t


  • 代码始终可以将无符号类型转换为另一个无符号类型。转换可消除否则可能发生的警告。

  • 如果目标类型是子范围,则信息可能会丢失。

这样就很好了。

uintptr_t ullint_to_uintptr(unsigned long long int n) {
    return (uintptr_t)n;
}

但是否需要强制类型转换?可能不需要。代码可以使用#if来确定。

uintptr_t ullint_to_uintptr(unsigned long long int n) {
    // C allows this to be true as pointer size, and hence `intptr_t` is not in a tight relationship with `unsigned long long`.
    #if UINTPTR_MAX < ULLONG_MAX
      return (uintptr_t) n;
    #else
      return n;
    #endif
}

11

  • 我犯了一个错误,没有在标题中指定“不丢失位”(我刚刚编辑了它),无论如何,答案似乎是上述 3 种转换都不能保证没有位丢失。


    – 


  • @ParminderSingh 您在什么情况下需要这样做?


    – 

  • 不需要#if(有或没有它都会生成相同的代码),return n;这里转换为“ uintptr_t”。


    – 

  • 1
    @ParminderSingh我不确定 C 标准是否保证从unsigned char到 的转换uintptr_t不会丢失信息。 是的,确实如此,因为“无符号整数类型,其属性是任何指向 void 的有效指针都可以转换为此类型…”。并且每个可用的无符号整数类型必须至少与 一样大unsigned char。它没有明确保证,但考虑到无符号整数类型的等级,它必须是正确的。


    – 


  • 1
    @AndrewHenle 我没有想到这一点,虽然没有明确说明,但似乎隐含地保证了这一点,说得好!


    –