10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ZOZOテクノロジーズ #3Advent Calendar 2019

Day 3

VBScriptの関数呼び出しの罠

Last updated at Posted at 2019-12-02

以下の関数を呼び出しを行っているコードは、動きそうな気がしますが、実はエラーになります。

Sub2(hoge, fuga)

一方で、以下のコードは正常に動きます。

res = Func1(hoge)
res = Func2(hoge, fuga)
Sub1(hoge)

これらのコードはほぼ違いがないですが、なぜ片方はエラーになってしまうのでしょうか?

実はこの仕様は色々と複雑なので、順を追って説明したいと思います。

括弧の意味

まず、VBScriptの括弧にはいくつかの意味がありますので、それらを説明します。

  1. 演算子の優先順位を変更
  2. 配列の要素の参照
  3. 関数(FunctionやSub)の呼び出し
  4. 関数のシグネチャでByRefになっている引数に値渡しをする
' 1
avg = (a + b) / 2.0

' 2
res = arr(5)

' 3
length = Len("hogehoge")

' 4
Function Func(a ByRef, b ByRef)
  ' なんかしらの処理
End Function
' hogeは参照渡し、fugaは値渡し
Func2(hoge, (fuga))

関数呼び出しのときのルール

さらに、関数呼び出しのときもいくつかのルールがあります。

  1. 戻り値を変数に代入する時は引数を()で囲む必要がある
  2. 戻り値を変数に代入せずCallキーワードを使っている時は、引数を()で囲む必要がある
  3. 上記の1, 2以外の時には引数を()で囲ってはいけない
' 1
res = Func1(hoge) ' OK
res = Func1 hoge  ' NG

' 2
Call Sub1(hoge) ' OK
Call Sub1 hoge  ' NG

最後に、参照渡しに関するルールです。
参照渡しが可能なケースでは、変数は参照渡しが行われますが、引数が余分な括弧で囲まれている場合は値渡しが行われます。

' hogeは参照渡し、fugaは値渡し
Func2(hoge, (fuga))

冒頭のコードの解説

さて、これらのルールを考えることで、冒頭のコードのうち、片方がエラーになり、もう片方が正常に実行できる理由が説明できます。

まずは、以下のコードがエラーになる理由を説明します。
(1)のコードは関数呼び出しのルールに明らかに抵触しているためにエラーになります。
このコードは戻り値を引数に代入していませんし、また、Callキーワードを使っているわけでもありません。

Sub2(hoge, fuga) ' (1)

次に以下の(2)〜(4)の3つが正常に実行できる理由を説明します。
(2)と(3)は関数の戻り値を変数に代入しているために、()で囲っても問題ありませんし、また、()をなくすとエラーになります。
さて、では(4)はどうでしょうか?
一見すると、(1)と同じ理由でエラーになりそうですが、ここで参照渡しに関するルールが適用されるためにエラーになりません。
(4)でhogeを囲っている()は関数呼び出しのための()ではなく、参照渡しを値渡しに変換するための()です。
そして関数呼び出しのための()は省略されています。
そのため、(4)の関数呼び出しはエラーになりません。

res = Func2(hoge, fuga) ' (2)
res = Func1(hoge)       ' (3)
Sub1(hoge)              ' (4)

しかし、これは意図せずに値渡しになってしまっている危険性もあるコードなので、(5)のように書くことによって値渡しにしていることを明示的にしたほうが良いです。

Call Sub1((hoge))       ' (5)

まとめ

これらのことをまとめると、以下のことがわかります。
以下のコードで、refは参照渡し、valは値渡しで渡される引数です。

Func1 ref
Call Func1(ref)
result = Func1(ref)
Func2 ref, ref
Call Func2(ref, ref)
result = Func2(ref, ref)

Func1(val)
Call Func1((val))
result = Func1((val))
Func2 (val), ref
Func2 ((val)), ref
Call Func2((val), ref)
result = Func2((val), ref)

そして、以下のコードはエラーになります

Call Func1 hoge
Z = Func1 hoge
Func2(hoge, fuga)
Call Func2 hoge, fuga
Z = Func2 hoge, fuga

参考

公式ドキュメントのCallステートメントに関する部分にも同様のことが書いてありますが、具体例がなく、分かったような分からないような気がしていたので、自分でまとめてみました。

Call ステートメント

プロシージャを呼び出すとき、キーワード Call は省略できます。キーワード Call を使って引数を必要とするプロシージャを呼び出す場合は、引数リスト argumentlist をかっこで囲む必要があります。キーワード Call を省略するときは、引数リスト argumentlist を囲むかっこも省略する必要があります。Call ステートメントの構文で組み込み関数またはユーザー定義型関数を呼び出す場合、その関数の戻り値を取得することはできません。

10
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?