web-dev-qa-db-ja.com

size_tをunsigned long intにキャストしても安全ですか?

タイプsize_tの変数nの値を出力するポータブルな方法が必要です。 ANSI C89を使用しているため、z長さ修飾子を使用できません。私の現在のアプローチは、値をlong unsigned intにキャストすることです:

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

size_tunsigned intまたはlong unsigned intとして定義されている場合、これがどのように失敗するかはわかりません。キャストは安全ですか?

12

C89では、size_tは符号なし整数型として定義されています。将来の標準とは異なり、C89は符号なし整数型のリストを次のように定義しています。

  • unsigned char
  • 未署名のショート
  • 符号なし整数
  • 署名なしlong

そのため、C89のsize_tunsigned longより大きくなることはないため、キャストは常に安全です。これにより、未定義の動作が発生することはなく、常に完全な値を保持するのに十分な大きさになります。

注目に値する; C89標準は次のように述べています:「厳密に準拠するプログラムの動作を変更しない限り、準拠する実装に拡張機能(追加のライブラリ関数を含む)が含まれる場合があります」拡張機能を変更できないことを意味します。この動作-符号なし整数型が具体的にリストされており、したがって変更できないため、C89標準に引き続き準拠しています。

将来の標準では、これは保証ではなく、未定義の動作が発生することはありませんが、unsigned longsize_tよりも小さい場合、データが失われる可能性があります。つまり、誤ったデータがユーザーに表示されます。この状況では、「安全」とラベルを付けるのをためらいます。


追加の重要な注意事項として、この回答は、C89標準に準拠しているコンパイラを指します。 C89コンパイラが上記の点で「準拠していない」可能性があります。その場合、動作をC99以降の動作と同様に扱い、未定義の動作は見られませんが、size_tの場合はデータが失われる可能性があります。 unsigned longよりも大きい。ただし、明確にするために、これはC89標準に準拠していません。

これを超えて、標準(1.7コンプライアンス)の私の解釈は、拡張が「厳密に準拠するプログラム」の動作を変更してはならないため、事実を変更できないと述べていますsize_tは、準拠せずに最大でもunsigned longでなければなりません。 そのような拡張が存在するという事実は変わりません。たとえば、GNU GCCはunsigned long longを追加する拡張機能を提供します。私の見解では、これは非準拠ですが、実際には、そのようなことに対処する準備をする必要があります-標準はあなたがやっていることは完全に安全であると述べていますが、非準拠のコンパイラまたは拡張機能が使用されている場合、潜在的なデータ損失に備える必要があります


このトピックに関する以前のディスカッションについては、こちらをご覧ください: https://stackoverflow.com/a/39441237/95534

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

size_tunsigned intまたはlong unsigned intとして定義されている場合...キャストは安全ですか?

はい、キャストは安全であり、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を許可していますが、それは確かにまれです。 IMO、SIZE_MAX > ULONG_MAX淡い色の向こう側 です。


このようなテストは時々一般的ですが、可能ではありますが、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

タイプsize_tの変数nの値を出力するポータブルな方法が必要です。

しかし、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 = '\0';
  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);
}
3
chux