size_tをintに入れる警告は無視してよいか?

· · C, C++, コンパイラ警告, 整数オーバーフロー, GCC, Clang, MSVC, 情報セキュリティ, libxml2

Xでポスト はてなブックマーク
更新履歴(初版のみ・2026年11月14日公開)
初版公開

64ビットの環境でstrlenの結果をintの変数に入れると、コンパイラーの設定によっては「データが失われる可能性があります」という種類の警告が出ます。MSVCのC4267、clangの-Wshorten-64-to-32、GCCの-Wconversionがこれに当たります。123 件数が多くなりやすく、扱う文字列は短いからと無視されることのある警告です。

長さやサイズを受ける箇所では、この警告は無視できません。libxml2のCVE-2025-6021は、この代入をしていた2行が原因でした。ただし、警告は既定では出ないことが多く、キャストで消しても誤りは残ります。この記事では修正前のコードと同じ形の小さな例とlibxml2の本体を使って、どのオプションが何を指摘するかを実測します。CVEの仕組みは「libxml2のxmlBuildQNameで、なぜ容量の検査が働かなかったのか?」に書きました。

実測は2026年11月14日に、Ubuntu 24.04.5 LTS(x86-64)のGCC 13.3.0とclang 18.1.3で行いました。intが32ビット、longとsize_tが64ビットの環境です。64ビットのWindowsではlongが32ビットなので、あとの表に出てくる型の名前はそのままでは当てはまりません。1MSVCでは試しておらず、MSVCについて書いたことはMicrosoftの文書によります。

警告が指している変換

size_tはsizeof演算子の結果の型である符号なしの整数型で、この記事の環境では64ビットです。4 intは32ビットなので、2,147,483,647を超える長さはintで表せません。Cの規格は、符号付きの型へ変換する値がその型で表せないとき、結果は処理系定義になるか、処理系定義のシグナルが発生するとしています。5 GCCの文書には、幅Nビットの型へ変換するときは値を2のN乗を法として型の範囲に収め、シグナルは発生させないとあります。6

次の表はあとの節の実測でintに入った値です。

strlenの結果 intに入った値
5 5
2,147,483,637 2,147,483,637
2,147,483,648 −2,147,483,648
4,294,967,301 5

3行目は長さが負になり、4行目は約4GiBの長さが5になります。警告はこの2つのような変換が起きうる代入を指しています。

size_tの値をintに入れたときの結果64ビットのsize_tの長さがintの上限以下なら同じ値がintに入り、上限を超えると負の値や小さな正の値など別の値が入ることを示します。はいいいえsize_tの長さ(64ビット)intの上限以下か同じ値がintに入る別の値がintに入る負の値や小さな正の値

図1: 長さがintの上限を超えると、intには別の値が入ります。56

図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全5件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle

CVE-2025-6021の該当行と同じ形の例

修正前のlibxml2 2.14.3のxmlBuildQNameは、接頭辞とローカル名の長さをstrlenで求めてintの変数に入れ、その合計で呼び出し側のバッファの容量を確かめていました。7 この記事では同じ形の長さの計算だけを取り出した次の例を使います。

#include <stddef.h>
#include <string.h>

/* 修正前の xmlBuildQName と同じ形の長さの計算。 */
char *build(const char *name, const char *prefix, char *memory, int len) {
    int lenn, lenp;

    lenn = strlen(name);
    lenp = strlen(prefix);
    if (len < lenn + lenp + 2)
        return NULL;
    memcpy(memory, prefix, lenp);
    memory[lenp] = ':';
    memcpy(&memory[lenp + 1], name, lenn);
    memory[lenn + lenp + 1] = 0;
    return memory;
}

8行目と9行目がsize_tからintへの代入です。10行目はintどうしの足し算で、12行目と14行目はintの長さをsize_tの引数に渡しています。

CVEの報告の値は、名前が2,147,483,637バイト、接頭辞が10バイトでした。この値では、8行目と9行目の代入で値は変わりません。変わるのは10行目で、足し算の結果がintの上限を超えます。警告が出るのは、原因である8行目と9行目(長さをintに入れる行)です。結果がintに収まらなくなる10行目には出ません。

報告の値で誤りが起きるまでの順序strlenの結果をintに入れた時点では値は変わらず、int同士の足し算がintの上限を超えて容量の検査が偽になり、バッファの外へ書き込むことを示します。8・9行目:strlenの結果をintに入れる10行目:int同士で足す結果がintの上限を超える容量の検査が偽になるバッファの外へ書き込む

図2: 報告の値では、代入ではなく足し算で結果がintの上限を超えます。

警告を出すオプションと出さないオプション

上の例を、オプションを変えてCとしてコンパイルしました。

コンパイラーとオプション 8・9行目の代入 12・14行目のmemcpy
GCC 13.3.0:オプションなし、-Wall、-Wall -Wextra 警告なし 警告なし
GCC:-Wall -Wextra -Wint-conversion 警告なし 警告なし
GCC:-Wconversion 警告あり 警告あり(-Wsign-conversion)
clang 18.1.3:オプションなし、-Wall、-Wall -Wextra 警告なし 警告なし
clang:-Wint-conversion 警告なし 警告なし
clang:-Wshorten-64-to-32 警告あり 警告なし
clang:-Wconversion 警告あり(-Wshorten-64-to-32) 警告あり(-Wsign-conversion)

GCCの-Wconversionは8行目に次の警告を出します。

narrow.c:8:12: warning: conversion from 'size_t' {aka 'long unsigned int'} to 'int' may change value [-Wconversion]

clangの-Wshorten-64-to-32の警告は次のとおりです。

narrow.c:8:12: warning: implicit conversion loses integer precision: 'unsigned long' to 'int' [-Wshorten-64-to-32]

C++としてコンパイルすると、警告の出方が少し変わります。GCCの-Wconversionは8行目と9行目だけを警告し、12行目と14行目の符号についての警告は出しませんでした。

-Wallと-Wextraを付けても、どちらのコンパイラーもこの代入を警告しませんでした。GCCの文書で-Wallと-Wextraが有効にするオプションの一覧にも、-Wconversionはありません。3

libxml2の報告のやり取りでは、同じ種類の問題を探す手段として-Wshorten-64-to-32と-Wint-conversionが挙がっていました。8 このうち-Wint-conversionはGCCでもclangでも、整数とポインターの間の変換についての警告です。92 size_tからintへの変換は対象ではなく、実測でも警告は出ませんでした。

MSVCのC4267はsize_tからそれより小さい型への変換を検出したときに出るレベル3の警告です。Microsoftの文書の例ではsize_tの値をintの引数に渡す行に「64ビットのプラットフォームでだけC4267」と書かれています。レベル3はIDEの既定の設定で、コマンドラインのコンパイラーの既定はレベル1です。110

オプションごとの8・9行目の代入の扱い既定と-Wallと-Wextraでは警告が出ず、-Wint-conversionは整数とポインターの間の変換の警告なので対象外で、GCCの-Wconversionとclangの-Wshorten-64-to-32で警告が出ることを示します。オプションなし、-Wall、-Wextra警告なし-Wint-conversion対象外(整数とポインターの間の変換)GCCの-Wconversion8・9行目を警告clangの-Wshorten-64-to-32

図3: この代入を警告するのはGCCの-Wconversionとclangの-Wshorten-64-to-32です。

足し算のオーバーフローと警告

報告の値で結果がintの上限を超える10行目には、上のどのオプションでも警告が出ませんでした。10行目はintどうしの足し算で、型の変換がありません。そのため型の変換の警告の対象ではありません。結果がintに収まるかは実行時の値で決まります。

GCCに-Wstrict-overflow=5を付けると、10行目に次の警告が出ます。

narrow.c:10:5: warning: assuming signed overflow does not occur when reducing constant in comparison [-Wstrict-overflow]

これはオーバーフローを見つけたという警告ではありません。GCCの文書によると、レベル5は、コンパイラーが比較の中の定数を小さくした箇所を警告するもので、多くの比較に当てはまるので誤検知がとても多いとされています。11 コンパイラーは、符号付きの足し算がオーバーフローしないと仮定して比較を書き換えています。

10行目の誤りが分かるのは実行時です。あとの「実行時の検査で分かる範囲」で確かめます。

代入と足し算で誤りが分かる時点の違い8・9行目の代入は型の変換の警告でコンパイル時に分かり、10行目の足し算のオーバーフローはコンパイル時の警告では分からず、実行時の検査で、その値の入力があったときに分かることを示します。8・9行目の代入型の変換の警告で分かる10行目の足し算コンパイル時の警告では分からない実行時の検査で分かるその値の入力があったときだけ

図4: 警告で分かるのは代入までで、足し算のオーバーフローは実行時の検査で分かります。

libxml2 2.14.3全体での件数

libxml2 2.14.3のCMakeの設定はGCCとclangに-pedantic -Wall -Wextra -Wshadowなどを渡します。-Wconversionと-Wshorten-64-to-32は入っていません。12

件数は次の方法で数えました。CMakeの既定の構成から、Pythonのバインディング、テスト、付属のプログラムを外して構成します。そこでライブラリ本体になる39個のソースファイルを、プロジェクトのオプションを付けて1つずつ-fsyntax-onlyでコンパイラーにかけます。ヘッダーの中の警告も数え、同じファイル、行、桁の警告は1件とします。2.15.4のソースファイルは37個です。

プロジェクトのオプションだけでは、3つのバージョンのどれでも、GCCとclangの警告は0件でした。そこへオプションを1つ足すと、次の件数になりました。

バージョン GCCに-Wconversionを足す うち[-Wconversion]の警告 clangに-Wshorten-64-to-32を足す
2.14.3 953件 341件 251件
2.14.4 948件 339件 249件
2.15.4 915件 328件 245件

2.14.3のGCCの953件のうち、612件は符号の変化についての-Wsign-conversionの警告です。GCCの[-Wconversion]にはintからcharなどへの変換も含まれるので、clangの件数とは対象が違います。

xmlBuildQNameの2行はどちらのコンパイラーでも指摘されていました。

tree.c:183:12: warning: implicit conversion loses integer precision: 'unsigned long' to 'int' [-Wshorten-64-to-32]
tree.c:184:12: warning: implicit conversion loses integer precision: 'unsigned long' to 'int' [-Wshorten-64-to-32]

修正後の2.14.4では、clangの件数が2件減っています。減ったのはこの2行の警告です。

2.14.3のclangの251件を型の組み合わせで分けると、次のようになります。

変換 件数
longからint 123
size_tからint 61
unsigned longからint 27
size_tからunsigned int 22
longからunsigned int 9
そのほか(intptr_t、ssize_t、uint64_tからint) 9

strlenの戻り値はclangの警告ではunsigned longと表示されます。size_tとunsigned longからintへの変換を合わせると88件です。

longからintの123件のうち118件は、警告の行に引き算がありました。そこから12件を抜き出して読むと、どれもポインターの引き算の結果をintに入れる行でした。これも長さを小さい型に入れる変換です。残りの5件は行番号を返すxmlGetLineNoの結果をintに入れる行です。

ファイル別ではencoding.cの29件が最も多く、parser.cの26件、uri.cとHTMLparser.cの20件が続きます。

libxml2 2.14.3での警告の件数の内訳プロジェクトのオプションでは警告が0件で、clangに-Wshorten-64-to-32を足すと251件になり、そのうちsize_tとunsigned longからintへの変換が88件で、xmlBuildQNameの2行がそこに含まれることを示します。プロジェクトのオプション:0件-Wshorten-64-to-32を足す:251件size_tとunsigned longからint:88件tree.cの183・184行を含む

図5: プロジェクトのオプションでは0件だった警告が、オプションを1つ足すと251件になります。

キャストで警告を消した場合

件数を減らす早い方法はキャストです。例の4か所にキャストを付けます。

lenn = (int)strlen(name);
lenp = (int)strlen(prefix);
memcpy(memory, prefix, (size_t)lenp);
memcpy(&memory[lenp + 1], name, (size_t)lenn);

このコードはGCCとclangのどちらでも、-Wall -Wextra -Wconversionを付けて警告が0件になりました。GCCの文書にも、-Wconversionは明示的なキャストを警告しないとあります。3

動作は変わりません。キャストを付けたコードを、次の節と同じclangの-fsanitize=implicit-conversion,signed-integer-overflowでビルドし、名前の長さを2,147,483,637バイトにして動かしました。キャストのないコードと同じ10行目のオーバーフローが報告され、そのあとプログラムはmemcpyの中の書き込みで止まりました。

cast.c:10:27: runtime error: signed integer overflow: 2147483647 + 2 cannot be represented in type 'int'

名前の長さを2,147,483,648バイトにした場合は、変換についての報告は出ず、プログラムはmemcpyの中のメモリアクセスの違反で止まりました。キャストを付けると、この代入は警告の対象からも、次の節で使う実行時の変換の検査の対象からも外れます。

キャストを付けたあとに残るものキャストを付けると警告は0件になるが、intに入る値は同じで、足し算のオーバーフローも残り、実行時の変換の検査の対象からも外れることを示します。代入にキャストを付ける警告は0件になるintに入る値は変わらない足し算のオーバーフローは残る実行時の変換の検査から外れる

図6: キャストを付けると警告が消え、実行時の変換の検査の対象からも外れますが、誤りは残ります。

実行時の検査で分かる範囲

clangのUndefinedBehaviorSanitizerには、暗黙の整数の変換で値が変わったことを実行時に報告する検査があります。clangの文書は、この検査が見つけるものは未定義の動作ではないが、意図していないことが多いと説明しています。-fsanitize=undefinedには含まれないので、-fsanitize=implicit-conversionを指定します。13

キャストのない例を-fsanitize=implicit-conversion,signed-integer-overflowでビルドし、名前の長さを変えて動かしました。接頭辞は10バイト、バッファは100バイトです。

名前の長さ 結果
5 報告なし。バッファに書き込んで終了
2,147,483,637 10行目の足し算のオーバーフローを報告。そのあとmemcpyの中で停止
2,147,483,648 8行目の変換で値が−2,147,483,648になったと報告。14行目の符号の変化も報告し、memcpyの中で停止
4,294,967,301 8行目の変換で値が5になったと報告。処理は続き、バッファに書き込んで終了

4行目の報告は次のとおりです。

narrow.c:8:12: runtime error: implicit conversion from type 'unsigned long' of value 4294967301 (64-bit, unsigned) to type 'int' changed the value to 5 (32-bit, signed)

この長さでは、サニタイザーを付けずにGCCでビルドしたプログラムは、何も報告せずに正常に終了しました。コードのとおりなら、書かれるのは名前の先頭の5バイトだけです。バッファの外への書き込みは起きませんが、結果は呼び出し側が渡した名前と違います。

GCCの-fsanitize=undefinedでビルドした場合は、2,147,483,637バイトで足し算のオーバーフローが報告され、2,147,483,648バイトでは何も報告されずにメモリアクセスの違反で止まりました。

実行時の検査が報告するのは、その値の入力で動かしたときだけです。この例で報告を出すには、2GiB前後かそれ以上の名前を渡すテストが要ります。

実行時の検査が報告する箇所の分かれ方8行目の代入で値が変われば変換の検査が8行目を報告し、変わらずに10行目の足し算がintの上限を超えれば足し算の検査が10行目を報告し、どちらでもなければ報告が出ないことを示します。はいいいえはいいいえ8行目の代入で値が変わるか変換の検査が8行目を報告10行目の足し算が上限を超えるか足し算の検査が10行目を報告報告なし

図7: 実行時の検査が報告する行は、代入で値が変わるか、足し算が上限を超えるかで分かれます。

型をsize_tにそろえる直し方

libxml2の修正は長さをstrlenの戻り値と同じsize_tで持つように変えました。14 例を同じ形に直します。

#include <stddef.h>
#include <stdint.h>
#include <string.h>

/* 修正後の xmlBuildQName と同じ形の長さの計算。 */
char *build(const char *name, const char *prefix, char *memory, int len) {
    size_t lenn, lenp;

    if (len < 0)
        return NULL;
    lenn = strlen(name);
    lenp = strlen(prefix);
    if (lenn >= SIZE_MAX - lenp - 1)
        return NULL;
    if ((size_t) len < lenn + lenp + 2)
        return NULL;
    memcpy(memory, prefix, lenp);
    memory[lenp] = ':';
    memcpy(&memory[lenp + 1], name, lenn);
    memory[lenn + lenp + 1] = 0;
    return memory;
}

このコードはGCCとclangのどちらでも、-Wall -Wextra -Wconversionを付けて警告が0件でした。前の節の4つの長さで動かすと、5バイトでは書き込み、ほかの3つではNULLを返し、サニタイザーの報告は出ませんでした。

直す途中では、警告から次に直す箇所が分かります。15行目のlenとの比較から(size_t)を外すと、どちらのコンパイラーも-Wextraでその行を警告しました。C++としてコンパイルした場合は、GCCは-Wallだけでこの行を警告しました。次はclangの警告です。

fixed_nocast.c:15:13: warning: comparison of integers of different signs: 'int' and 'size_t' (aka 'unsigned long') [-Wsign-compare]

lenはintの引数なので、負の値を先に除いてからsize_tに直して比較します。ここでのキャストは前の節のキャストと違い、直前のlen < 0の検査で値の範囲を確かめたあとに付けています。

APIの都合でintに入れるしかない場合は、入れる前に値がINT_MAX以下であることを確かめ、超えていればエラーにします。この検査を通った値でも、intどうしで足せば上限を超えることがあります。CVEの報告の値はどちらもINT_MAX以下でした。足し算はsize_tのまま済ませ、結果を確かめてからintに入れます。

型をsize_tにそろえる手順長さをsize_tで持ち、足す前に結果がsize_tに収まることを確かめ、-Wsign-compareの警告が出るintのlenとの比較は、負の値を除いてからsize_tに直して行うことを示します。長さをsize_tで持つ足す前に結果が収まるか確かめるlenとの比較に-Wsign-compareの警告が出る負のlenを先に除くsize_tに直して比較する

図8: 型をそろえると、intのまま残ったlenとの比較に-Wsign-compareの警告が出ます。

既存のコードで警告を有効にする順序

libxml2 2.14.3の件数は、既存のコードでこの警告を有効にしたときに起きることの例です。0件だった警告が251件や953件になり、一度に全部は読めません。ここからは実測ではなくこの件数を前提にした進め方の案です。

  1. 新しく書くコードでは長さとサイズをsize_tで持ち、GCCなら-Wconversion、clangなら-Wshorten-64-to-32を付けてビルドする。MSVCではC4267を無効にしない。
  2. 既存のコードでは警告を型の組み合わせで分け、size_tからintへの変換と、ポインターの引き算の結果をintに入れる変換を先に読む。libxml2 2.14.3のclangの結果では、251件のうち前者が88件で、後者は多くても118件だった。
  3. その中で、入れた値を確保やコピーの大きさ、容量の比較に使っている箇所を先に直す。xmlBuildQNameの2行はこの形だった。
  4. キャストは値が収まることを直前の検査で確かめた箇所にだけ付ける。
既存のコードで警告を読む順序警告を型の組み合わせで分け、size_tやポインターの引き算の結果といった長さをintに入れる変換を先に読み、その値を確保やコピーの大きさや容量の比較に使う箇所から直し、キャストは範囲を確かめた箇所にだけ付けることを示します。警告を型の組み合わせで分ける長さをintに入れる変換を先に読む大きさや容量の比較に使う箇所から直す範囲を確かめた箇所にだけキャストを付ける

図9: 件数が多いときは、長さを小さい型に入れて大きさの計算に使う箇所から読みます。

関連記事

参考資料

  1. Microsoft Learn, Compiler Warning (level 3) C4267. 参照箇所: 冒頭のメッセージ'var' : conversion from 'size_t' to 'type', possible loss of data、「Remarks」、「Example」のFunc2(bufferSize); // C4267 only for 64-bit platforms(Func2はintの引数)とFunc3(bufferSize); // C4267 only for 64-bit platforms(Func3はlongの引数)。 ↩ ↩2 ↩3

  2. LLVM Project, Clang Diagnostics Reference. 参照箇所: -Wshorten-64-to-32のimplicit conversion loses integer precision: A to B、-Wint-conversionのincompatible integer to pointer conversion。開発中のバージョンの文書で、実測はclang 18.1.3で行った。 ↩ ↩2

  3. GCC, GCC 13.3のWarning Options. 参照箇所: -Wconversionの項。検索語Warn for implicit conversions that may alter a value、Do not warn for explicit casts。-Wallと-Wextraの項のオプションの一覧に-Wconversionがないこと。 ↩ ↩2 ↩3

  4. ISO/IEC JTC1/SC22/WG14, N1570(C11の最終草案). 参照箇所: 7.19の第2段落のsize_t(紙面288ページ/PDF表示306ページ)。 ↩

  5. ISO/IEC JTC1/SC22/WG14, N1570(C11の最終草案). 参照箇所: 6.3.1.3の第3段落(紙面51ページ/PDF表示69ページ)。 ↩ ↩2

  6. GCC, GCC 13.3のIntegers implementation. 参照箇所: 検索語the value is reduced modulo 2^N to be within range of the type; no signal is raised。 ↩ ↩2

  7. GNOME GitLab, libxml2 2.14.3のtree.c. 参照箇所: 183行と184行のstrlenの代入、186行のlen < lenn + lenp + 2。 ↩

  8. GNOME GitLab, libxml2 Issue #926. 参照箇所: 検索語-Wshorten-64-to-32(Nick Wellnhofer)、-Wint-conversion(Michael Catanzaro)。 ↩

  9. GCC, GCC 13.3のWarning Options. 参照箇所: -Wno-int-conversionの項。検索語incompatible integer to pointer and pointer to integer conversions。 ↩

  10. Microsoft Learn, /w, /W0, /W1, /W2, /W3, /W4, /w1, /w2, /w3, /w4, /Wall, /wd, /we, /wo, /Wv, /WX (Warning level). 参照箇所: 検索語/W1 is the default setting in the command-line compiler、/W3 is the default setting in the IDE。 ↩

  11. GCC, GCC 13.3のWarning Options. 参照箇所: -Wstrict-overflow=5の項。検索語reduces the magnitude of a constant involved in a comparison、a very large number of false positives。 ↩

  12. GNOME GitLab, libxml2 2.14.3のCMakeLists.txt. 参照箇所: 405〜408行の-pedantic -Wall -Wextra -Wshadow -Wpointer-arith -Wcast-align -Wwrite-strings -Wstrict-prototypes -Wmissing-prototypes -Wno-long-long -Wno-format-extra-args -Wno-array-bounds。 ↩

  13. LLVM Project, UndefinedBehaviorSanitizer. 参照箇所: -fsanitize=implicit-unsigned-integer-truncation, -fsanitize=implicit-signed-integer-truncationの項。検索語Issues caught by these sanitizers are not undefined behavior, but are often unintentional。-fsanitize=undefinedの項のother than float-divide-by-zero, unsigned-integer-overflow, implicit-conversion。 ↩

  14. GNOME GitLab, libxml2 commit ad346c9a「tree: Fix integer overflow in xmlBuildQName」. 参照箇所: tree.cの差分。 ↩

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

size_tをintに入れる警告(C4267など)は無視してよいですか?
長さやサイズを受ける箇所では無視できません。libxml2のCVE-2025-6021は、strlenの結果をintに入れて足し算をしていた箇所が原因でした。GCC 13.3.0の-Wconversionとclang 18.1.3の-Wshorten-64-to-32は、修正前のその2行を指摘します。
-Wallと-Wextraを付けていれば、size_tからintへの変換は警告されますか?
手元のGCC 13.3.0とclang 18.1.3では、どちらも警告されませんでした。GCCでは-Wconversion、clangでは-Wshorten-64-to-32か-Wconversionを足すと警告されます。-Wint-conversionは整数とポインターの間の変換の警告で、この変換は対象ではありません。
キャストを付けて警告を消せば解決ですか?
解決しません。この記事の例では、キャストを付けると警告は0件になりましたが、intに入る値は変わらず、足し算のオーバーフローもバッファの外への書き込みも同じように起きました。長さをsize_tのまま持つか、intに入れる前に値の範囲を確かめます。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る