2016年3月19日

VisualStudio と offsetof と参照型 / VisualStudio and offsetof and reference types

Windows 環境でのみ発生するクラッシュに 1 日ほど悩まされたので、その原因について。

I got troubled by a Windows-only crash and spent 1 day. Here's the reason of the crash.

offsetof ってどうコンパイルされんの? / How does offsetof get compiled?

とりあえずは公式のドキュメントを見ましょう。 offsetof Macro によると、以下のようにオフセットを返すという事くらいしか書かれていません。

Let's check the official document as a first step. offsetof Macro says only that it returns the offset.

The offsetof macro returns the offset in bytes of memberName from the beginning of the structure specified by structName as a value of type size_t. You can specify types with the struct keyword.

これでは何も分からないので、実際にコンパイルして出てきたコードを見てみましょう。

It doesn't help much, so let's check out the generated code.

こちらが今回使用するコードです。

Here's the source code for testing.

// offsetof_ptr.cpp

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

class C {
  int32_t* a;
  int32_t* b;

public:
  static size_t offsetOfB() {
    return offsetof(C, b);
  }
};

int
main(void) {
  fprintf(stderr, "%d\n", C::offsetOfB());
  return 0;
}

C::offsetOfB メソッドは、C クラスの b メンバのオフセットを返してくれるハズです。コンパイルには VisualStudio 2013 Community の cl コマンドを使用します。

C::offsetOfB method should return the offset of b member of C class. I use cl command of VisualStudio 2013 Community to compile it.

E:\> cl offsetof_ptr.cpp
E:\> offsetof_ptr.exe
4

返してくれました。では、どういう仕組みで計算してるのでしょうか。

It does. So, how does it calculate the offset?

E:\> cl /P offsetof_ptr.cpp
E:\> cl /FA offsetof_ptr.cpp

まずはプリプロセスの結果。

First, here's the result of preprocessing.

// offsetof_ptr.i with VisualStudio 2013, code for offsetOfB

  static size_t offsetOfB() {
    return (size_t)&reinterpret_cast<const volatile char&>((((C *)0)->b));

offsetof が書き変わっています。という事で、マクロの類いである事が分かります。

offsetof is replaced. It means that it would be a kind of macro.

次にアセンブリでは、即値として 4 を返しています。

Next, in assembly, it returns an immediate value 4.

; offsetof_ptr.asm with VisualStudio 2013, assembly for C::offsetOfB

; Function compile flags: /Odtp
;       COMDAT ?offsetOfB@C@@SAIXZ
_TEXT   SEGMENT
?offsetOfB@C@@SAIXZ PROC                                ; C::offsetOfB, COMDAT
; File e:\offsetof_ptr.cpp
; Line 10
        push    ebp
        mov     ebp, esp
; Line 11
        mov     eax, 4
; Line 12
        pop     ebp
        ret     0
?offsetOfB@C@@SAIXZ ENDP                                ; C::offsetOfB
_TEXT   ENDS

参照型と一緒に使うとどうなる? / What happens if I use it with reference types?

問題はこの offsetof を参照型のメンバに使用するとどうなるか、という事です。offsetof_ptr.cpp から、b の型をポインタから参照に変えてみましょう。

The problem is, what happens if I use offsetof to a reference-typed member. Let's change the type of b from a pointer to a refernce, in offsetof_ptr.cpp.

// offsetof_ref.cpp

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

class C {
  int32_t* a;
  int32_t& b;

public:
  static size_t offsetOfB() {
    return offsetof(C, b);
  }
};

int
main(void) {
  fprintf(stderr, "%d\n", C::offsetOfB());
  return 0;
}

とりあえず実行してみます。

Let's give it a try.

E:\> cl offsetof_ref.cpp
E:\> offsetof_ref.exe

クラッシュしました。

it crashes.

という事で、生成されたコードを見てみましょう。

So, let's check the generated code.

E:\> cl /P offsetof_ref.cpp
E:\> cl /FA offsetof_ref.cpp
// offsetof_ref.i with VisualStudio 2013, code for offsetOfB

  static size_t offsetOfB() {
    return (size_t)&reinterpret_cast<const volatile char&>((((C *)0)->b));
  }

offsetof の変換結果に差がありません。

There is no difference in the conversion result of offsetof.

    return (size_t)&reinterpret_cast<const volatile char&>((((C *)0)->b));

これでは、メンバのアドレスではなくメンバの値を返しています。

With this way, it returns the value of the member, not the address of the member.

念のため、アセンブリでも見てみましょう

Just to be sure, let's check the assembly.

; offsetof_ref.asm with VisualStudio 2013, assembly for C::offsetOfB

; Function compile flags: /Odtp
;       COMDAT ?offsetOfB@C@@SAIXZ
_TEXT   SEGMENT
?offsetOfB@C@@SAIXZ PROC                                ; C::offsetOfB, COMDAT
; File e:\offsetof_ref.cpp
; Line 10
        push    ebp
        mov     ebp, esp
; Line 11
        mov     eax, 4
        mov     eax, DWORD PTR [eax]
; Line 12
        pop     ebp
        ret     0
?offsetOfB@C@@SAIXZ ENDP                                ; C::offsetOfB
_TEXT   ENDS

明かに 4 をデリファレンスしてくれてます。

It clearly dereferences 4.

        mov     eax, 4
        mov     eax, DWORD PTR [eax]

生成されたコードは Visual Studio 2015 Community でも全く同じでした。

The generated code is exactly same for Visual Studio 2015 Community too.

結論としては、offsetof は参照型のメンバに使えない、使うとクラッシュする、という事でした。

So, as a conclusion, offsetof cannot be used to a reference-typed member, and if you use, it crashes.

clang と gcc はどうやってんの? / How do clang and gcc handle this?

こちらは clang++ のプリプロセス結果とアセンブリ。

This is the result of preprocess and the assembly with clang++.

// offsetof_ref.ii with clang++ 700.1.81, code for offsetOfB

  static size_t offsetOfB() {
    return __builtin_offsetof(C, b);
  }
; offsetof_ref.s with clang++ 700.1.81, assembly for C::offsetOfB

__ZN1C9offsetOfBEv:                     ## @_ZN1C9offsetOfBEv
## BB#0:
        pushl   %ebp
        movl    %esp, %ebp
        movl    $4, %eax
        popl    %ebp
        retl

こちらは g++ のプリプロセス結果とアセンブリ。

and this is the result of preprocess and the assembly with g++.

// offsetof_ref.ii with g++ 4.9.2, code for offsetOfB

  static size_t offsetOfB() {
    return __builtin_offsetof (C, b);
  }
; offsetof_ref.s with g++ 4.9.2, assembly for C::offsetOfB

_ZN1C9offsetOfBEv:
.LFB0:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        movl    $4, %eax
        popl    %ebp
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc

どちらも __builtin_offsetof という組み込み関数を使用していて、それが参照型の場合にも上手くやってくれているようです。

Both of them use the builtin function __builtin_offsetof, and it seems to handle the reference types as well.

2016年3月16日

RegExp.multiline スイッチが消えます

この記事は全体として、まもなく消えるスーパー非推奨な機能の挙動について扱っています。コードを書く時の参考にしたりしないでください。

SpiderMonkey に JavaScript 1.2 の頃からある機能に RegExp.multilineRegExp['$*'] っていうのがあります。 これはどちらも同じもので、ES6 でいうところの m フラグ相当のもの (^$ を行頭と行末にマッチさせるかどうか) を、 グローバルな状態として切り替え可能にしておこう、っていう機能です。

2016-03-16 の時点での挙動としては、新規の生成する RegExp オブジェクトの m フラグに反映されるようになってます。

# 初期状態は false です

js> RegExp.multiline
false
js> RegExp['$*']
false
js> /a/
/a/

# true にするとこれから作成する RegExp インスタンスに m フラグが付きます

js> RegExp.multiline = true
true
js> RegExp['$*']
true
js> /a/
/a/m

# RegExp コンストラクタも同様です

js> new RegExp("a")
/a/m

# false に戻すと m フラグは付かなくなります

js> RegExp.multiline = false
false
js> /a/
/a/

# 先に定義しておいた関数内にも効きます

js> function f() { return /a/; }
js> f()
/a/
js> RegExp.multiline = true
true
js> f()
/a/m

そんな RegExp.multiline ですが、現在では非標準な上に内部での扱いがとてつもなくめんどくさいので、bug 1219757 で削除されます。おそらく Firefox 48 のうちに FIXED になるでしょう。

一方 m フラグは、少し遅れて JavaScript 1.5 で追加され、標準化されました。なので、もし bug 1219757 が FIXED になった後でアドオンなんかが動かなくなった、という場合には RegExp.multilineRegExp['$*'] を使用しているコードを探し出して、m フラグを使うように書きかえてください。