将 size_t 转换为 unsigned long int 是否安全?

Is it safe to cast size_t to unsigned long int?

我需要一种可移植的方式来打印类型为 size_t 的变量 n 的值。因为我使用 ANSI C89,所以我不能使用 z 长度修饰符。我目前的做法是将值转换为 long unsigned int:

printf("%lu\n", (long unsigned int) n);

假设 size_t 定义为 unsigned intlong unsigned int 我看不出这会失败。施法安全吗?

C89中,size_t被定义为无符号整数类型。与未来的标准不同,C89定义了无符号整数类型列表如下:

  • 无符号字符
  • 无符号短
  • 无符号整数
  • 无符号长整数

因此,C89 中的 size_t 永远不会大于 unsigned long,因此转换始终是安全的 - 因为它不会导致任何未定义的行为,而且它总是足够大以容纳整个价值。

值得注意; C89 标准规定: "A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any strictly conforming program" 这意味着没有扩展可以改变这种行为——同时仍然符合 C89 标准,因为无符号整数类型已被特别列出,因此不能改变。

在未来的标准中,这不是保证,虽然您不会得到未定义的行为 - 您可能会丢失 unsigned long 小于 size_t 的数据,这意味着您会显示不正确的数据你的用户。在这种情况下,我会犹豫是否将其标记为 "safe".


作为重要的附加说明;此答案指的是符合 C89 标准的编译器。在上述方面,您的 C89 编译器可能是 "less than compliant",在这种情况下 - 将行为视为类似于 C99 或更新版本的行为,您不会看到未定义的行为,但如果 size_t 大于 unsigned long。需要明确的是,这不符合 C89 标准。

除此之外,我对标准(1.7 合规性)的解释是,虽然它声明扩展不得改变 "strictly conforming program" 的行为,因此不能改变 size_t 最多必须 unsigned long 的事实而不遵守;它不会改变 这样的扩展确实存在 的事实。例如 GNU GCC 确实提供了添加 unsigned long long 的扩展。在我看来,这是不合规的,但现实是你必须准备好处理这样的事情 - 虽然标准说你正在做的事情是完全安全的,但你必须为潜在的数据做好准备使用不兼容的编译器或扩展的损失


有关此主题的先前讨论,请参阅此处:

size_t n = foo();
printf("%lu\n", (long unsigned int) n);

Provided that size_t is defined as either unsigned int or long unsigned int ... Is the cast safe?

是的,C89、C99、C11 上的转换是安全的,没有未定义的行为,也没有信息丢失。


但是 如果条件不成立会怎样

假设size_t的范围会在unsigned long的范围内是很合理的。添加编译时测试:ref

#include <limits.h>
#if defined(__STDC__)
#if defined(__STDC_VERSION__)
#if (__STDC_VERSION__ >= 199901L)
#include <stdint.h>
#if SIZE_MAX > ULONG_MAX
#error Re-work printf size code
#endif
#endif
#endif
#endif

关键是当代码有依赖时 - 添加一个测试。即使它在今天和历史上所有已知的机器上都可以接受,但未来还是未知数。

C,在今天,以其巨大的灵活性确实允许 SIZE_MAX > ULONG_MAX,但它肯定是很少见的。国际海事组织,SIZE_MAX > ULONG_MAXbeyond the pale


这样的测试不时很常见,虽然可能,但编写 super 可移植代码根本不可行或预算不多。

#include <limits.h>
#if CHAR_BIT != 8 && CHAR_BIT != 16 && CHAR_BIT != 32 && CHAR_BIT != 64
  #error Code depends on char size as a common power of 2.
#endif

I need a portable way to print the value of a variable n of type size_t.

然而,为了实现 OP 的顶级目标,可以编写一个简单的可移植辅助函数。

// This approach works with any unsigned type
void print_size_t(size_t n) {
  if (n >= 10) print_size_t(n/10);
  putchar((int) (n%10) + '0');
}

为了避免递归,稍微长一点的函数:

#include <limits.h>
void print_size_t(size_t n) {
  char buf[sizeof n * CHAR_BIT / 3 + 2];  // 1/3 is more than log2(10)
  char *p = &buf[sizeof buf - 1];          // Start at end of buf[]
  *p = '[=14=]';
  do {
    p--;
    *p = (char) (n%10 + '0');
    n /= 10;
  } while (n);    // Use a do {} while so print_size_t(0) prints something
  fputs(p, stdout);
}