🚀The world's best VBA AI has evolved. ExcelMaster is now an autonomous Agent.Read more →
Back to Blog

Excel VBA の UBound — UBound と LBound で配列のサイズを知る(1 つずれる罠)

|

Excel VBA の UBound — UBound と LBound で配列のサイズを知る(1 つずれる罠)

TL;DR — VBA では、配列を 数える のではなく、配列に 尋ねますUBound(a) は有効な最大の添字を 返し、LBound(a) は最小の添字を返します。配列は 既定で 0 始まり なので、Dim a(9) は添字 0 から 9 までの 10 要素を持ちます — UBound10 ではなく 9 です。この 1 つのずれが、ループで最もよく 出るバグです。いつでも生き残るループは For i = LBound(a) To UBound(a) であり、本当の要素数は UBound(a) - LBound(a) + 1 です。配列に .Length.Count はありません。

Sub LoopSafely()
    Dim a() As String
    a = Split("red,green,blue", ",")   ' Split は 0 始まりの配列を返す

    Dim i As Long
    For i = LBound(a) To UBound(a)      ' 上限・下限が何であれ動く
        Debug.Print i, a(i)            ' 0 red / 1 green / 2 blue
    Next i

    Debug.Print "count = " & (UBound(a) - LBound(a) + 1)   ' 3
End Sub

UBound は配列の形を読み取る手段であり、ReDim の相棒です。配列にサイズを与え、そ れからどんなサイズになったかを尋ねます。上限・下限を間違えると、最後の要素を飛ばすか、末尾を越えて実行時 エラー 9 に突っ込むかのどちらかです。配列を渡してくる相手によって開始の添字が違うので、01 だと決 めてかからず 尋ねる 習慣こそが、ループを正しく保ちます。

この記事で学べること

  • UBoundLBound が何を返すのか、そして配列に .Length プロパティがない理由
  • VBA の配列が既定で 0 始まりである理由、そして 1 つずれるバグがどこから来るのか
  • いつでも動くループ — For i = LBound(a) To UBound(a)
  • 本当の配列の要素数 — UBound(a) - LBound(a) + 1
  • 二次元配列の各次元のサイズを UBound(a, 2) で得る
  • 空の動的配列に UBound を使うとなぜ実行時エラー 9 になるのか、そしてそれをどう守るか

UBound と LBound:数えるのではなく、尋ねる

UBound(上限)は合法に使える最大の添字を返し、LBound(下限)は最小の添字を返します。二つ合わせて配列 の形を表します。VBA の配列には、あえて .Length.Countありません — 上限・下限こそが正しさの よりどころであり、それを実行時に読み取ります。

Dim a(1 To 5) As Long
Debug.Print LBound(a)   ' 1
Debug.Print UBound(a)   ' 5

なぜ覚えておくのではなく尋ねるのか? ループで回している配列は、たいてい別のどこかでサイズが決められている からです — ReDim で、Split で、範囲を読み込んで — そして自分が そうだと思って いる サイズを直書きすることこそ、ループが現実とずれていく原因なのです。

既定で 0 始まり:1 つずれが潜む場所

特に指定しなければ、VBA の配列は添字 0 から始まります。ですから Dim a(9) は 9 要素を与えるのでは ありません — 添字 0 から 9 までの 10 要素を与えます。

Dim a(9) As Long        ' 10 要素: a(0) .. a(9)
Debug.Print LBound(a)   ' 0
Debug.Print UBound(a)   ' 9  <- 10 ではない

これが典型的なバグの根っこです。For i = 1 To UBound(a)a(0) を黙って飛ばし、For i = 0 To 10 は一 歩行き過ぎて Subscript out of range にぶつかります。Option Base 1 でモジュール全体を 1 始まりに強制し たり、Dim a(1 To 10) で明示的に上限・下限を宣言したりもできますが — 本当の直し方は、開始をそもそも推測 するのをやめることです。

いつでも動くループ

ループを配列自身の上限・下限で書けば、配列がどう宣言されたか、どこから来たかによらず正しくなります。

Dim i As Long
For i = LBound(a) To UBound(a)
    ' ... a(i) を使う ...
Next i

この一つの習慣が、1 つずれるエラーの一族をまるごと取り除きます。0 始まりの Split の結果でも、1 始まりの Range.Value 配列でも、Option Base 1 のモジュールでも、明示的な Dim a(5 To 12) でも等しく動きます。 LBound がただで本当のことを教えてくれるのに、配列ループの開始に 01 を直書きしてはいけません。

配列の要素数:UBound から LBound を引いて 1 を足す

配列はどんな添字からでも始まれるので、その要素数は単なる UBound + 1 ではありません。いつでも正しい式は これです。

Dim n As Long
n = UBound(a) - LBound(a) + 1

10 要素の 0 始まり配列なら 9 - 0 + 1 = 10Dim a(1 To 10) なら 10 - 1 + 1 = 10 です。この完全な式を 使えば、配列がどの基点を使っていても関係ありません。(「VBA の配列の要素数」を返すプロパティを探す人がい ますが、そんなものはありません — この式が答えです。)

二次元配列:次元ごとに上限・下限がある

多次元配列では、UBoundLBound は二つめの引数 — どの次元について尋ねているか — を取ります。 UBound(a, 1) は最初の次元(行)、UBound(a, 2) は二つめの次元(列)です。

Dim g(1 To 3, 1 To 4) As Long   ' 3 行、4 列
Debug.Print UBound(g, 1)        ' 3  (行)
Debug.Print UBound(g, 2)        ' 4  (列)

Dim r As Long, c As Long
For r = LBound(g, 1) To UBound(g, 1)
    For c = LBound(g, 2) To UBound(g, 2)
        ' ... g(r, c) を使う ...
    Next c
Next r

次元番号のない素の UBound(g) は、最初の次元を返します。これが最も効いてくるのは、シートを配列に読み込 むときです。Range.Value はつねに 1 始まりの二次元配列を渡してくるので — UBound(a, 1)UBound(a, 2) を尋ねることが、実際に何行何列を受け取ったかを知る方法になります。

空の動的配列を守る

宣言されたものの一度も ReDim されていない動的配列(あるいは Erase された配列)には、上限・下限がま ったくありません。それに UBound を呼ぶと、実行時エラー 9、Subscript out of range が発生します。

Dim a() As Long
Debug.Print UBound(a)   ' エラー 9 - 配列にはまだサイズがない

配列が空である可能性が少しでもあるなら — たとえば何にも一致しなかった Filter から来た場合など — ループ の前に守りましょう。小さなヘルパーを使うと、呼び出し側のコードがすっきりします。

Function IsAllocated(a As Variant) As Boolean
    On Error Resume Next
    IsAllocated = (LBound(a) <= UBound(a))
    On Error GoTo 0
End Function

あとは If IsAllocated(a) Then For i = LBound(a) To UBound(a) ... です。これは、オブジェクトを使う前に Is Nothing で守るのと、配列における同類です。

ExcelMaster の活用

UBound は小さな関数ですが、一つの大きな仕事を持っています — ループにどこで止まればいいかを正確に教える ことです — そしてそのまわりの誤り(01 だと決めてかかる、配列が空だと忘れる、要素数を直書きする) こそ、マクロが最後の行を飛ばしたり、境界のケースでクラッシュしたりする原因なのです。

ExcelMaster には、やりたいことをそのま ま伝えられます — 「使用範囲を配列に取り込んで、各列を合計して、その合計を下に書き出して」 — すると、正し い次元で LBound から UBound までループし、要素数を正しく計算し、空で返ってきたかもしれない配列を守る ので、マクロは最初の行でも、最後の行でも、空のケースでも正しく動きます。ブックとコードはあなたの手元に残 ります。

よくある質問

VBA の UBound は何をするのですか?

UBound は配列の有効な最大の添字 — 角かっこの中で使える最大の数 — を返します。Dim a(1 To 5) なら UBound(a)5、0 始まりの Dim a(9) なら 9 です。最小の添字を返す LBound と組み合わせれば、サイ ズを直書きせずに配列をループできます。

VBA で配列の要素数を得るには?

VBA の配列に .Length.Count プロパティはありません。要素数は UBound(a) - LBound(a) + 1 で求めま す。VBA の配列は添字 0、1、あるいは任意の明示的な下限から始まれるので、完全な式が必要です — UBound + 1 が正しいのは 0 始まりの配列だけです。

UBound で Subscript out of range が出るのはなぜですか?

たいていは配列が空だからです — Dim a() で宣言したものの一度も ReDim していない動的配列や、Erase さ れた配列には上限・下限がないので、UBound は実行時エラー 9 を発生させます。ループの前に、小さな IsAllocated ヘルパーで守りましょう。ループが UBound を越えて走るときにも実行時エラー 9 は出ます。たと えば 10 要素の 0 始まり配列に対する For i = 0 To 10 です。

VBA の配列は 0 始まりですか、1 始まりですか?

既定では 0 始まりです。Dim a(9) は添字 0 から 9 までの 10 要素を持ちます。Option Base 1 でモジュー ルを 1 始まりにしたり、Dim a(1 To 10) で明示的に上限・下限を設定したりできます。SplitArray が返 す配列は 0 始まり、Range.Value から読んだ配列は 1 始まりです。まちまちなので、決めてかからず LBound から UBound までループしましょう。

VBA で二次元配列のサイズを調べるには?

次元番号を二つめの引数として渡します。最初の次元(行)には UBound(a, 1)、二つめの次元(列)には UBound(a, 2)、下限にはそれぞれ LBound(a, 1)LBound(a, 2) です。これが、Range.Value が返す二次元 配列の形を読み取る方法です。

検証環境

動作確認: Excel 365 (Windows 11), VBA 7.1 — 最終確認 2026-09-24.

関連ガイド: VBA ReDim · VBA 2D Arrays · VBA Array · VBA For Loop · VBA Split