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
前段时间,我调查了所有可以找到编译器的半新平台。只有 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 int
,unsigned long int
也unsigned long long int
没有规则阻止它们中的任何一个比 更宽或更窄uintptr_t
。因此,C 标准无法保证将unsigned int
、unsigned long int
或转换unsigned long long int
为uintptr_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 int
或void*
,以较大者为准。甚至有一个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_t
有16 位长,就像要求至少有16 位长一样,但我们必须注意“至少”这个词,这意味着 可以是 32 位长,也可以是16 位长,这完全符合 C 标准。这意味着我提出的任何转换都不能保证没有位丢失。UINT_MAX
unsigned long
unsigned int
uintptr_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 int
吗uintptr_t
?
[OP 改变了一些东西]
我可以将一个无符号长整型转换为 uintptr_t 而不丢失位吗?
唯一保证不会丢失信息的无符号类型是从unsigned char
到uintptr_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 我没有想到这一点,虽然没有明确说明,但似乎隐含地保证了这一点,说得好!
–
|
–
long long
但只有 32 位指针。例如较旧的 32 位 MS VC,其中sizeof(uintptr_t)
是4
。所以不,您不一定能做到这一点。–
uintptr_t
唯一明确定义的事情是在和之间来回转换void*
。为什么需要将常规无符号整数转换为uintptr_t
?–
uintptr_t
这一事实。–
void*
可以用它来代替。unsigned long long
–
|