2014/08/27


追記: ほんとは怖くないよ!って話を追記してます。

夏だし怖い話しようぜ!

訳あって一部だけ C++ のコードです。

#include <stdio.h>
#include <iostream>

int
main() {
  char str[256];

  double a = 123.45;
  int b = 57;
  int c = 89;

  sprintf(str, "a=%f b=%d c=%d", a, b, c);
  std::cout << str << std::endl;

  sprintf(str, "a=%d b=%f c=%d", a, b, c);
  std::cout << str << std::endl;

  std::cout << "a=" << a << std::endl;
  std::cout << "b=" << b << std::endl;
  std::cout << "c=" << c << std::endl;

  return 0;
}

コンパイル時に警告は出るだろうけど、ちゃんと動くよね。

$ g++  -Wformat-nonliteral aaaa.cxx && ./a.out
aaaa.cxx: In function ‘int main()’:
aaaa.cxx:16:43: warning: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘double’ [-Wformat]
aaaa.cxx:16:43: warning: format ‘%f’ expects argument of type ‘double’, but argument 4 has type ‘int’ [-Wformat]
a=123.450000 b=57 c=89
a=57 b=123.450000 c=89
a=123.45
b=57
c=89

きぇーーーーーーーーーーーーーーー!

C++ でなく C++ の場合だけ発生します。(Cでも発生しました)

ちなみに 32bit Windows だと gcc4.8, gcc4.9, clang3.4.2, VS2010, VS2012, VS2013 では

123.450000 57 89
-858993459 0.000000 89

こう出力されました。64bit Linux の gcc4.6.3, gcc4.9.1 だと上記の結果が出力されました。wandbox でもこんな結果になります。

だれか理由を教えて下さい...

追記

Cの呼び出し規約の varargs で、期待する型と与えられた型のサイズ違い(sizeof(double)-sizeof(int))で発生する様です。(参考)

さらに勘違い。64bit OS の場合は int と double で格納されるレジスタが違うらしく(XMMレジスタ)、その取り出し順序が期待と異なるのが原因らしいです。(yohhoyさんありがとうございます)

さらに 32bit Windows の場合は単に順番にstackに積むだけなのでa[31:0]をintと、b:a[63:32]をdoubleと見做して解釈する事で発生するらしいです。(kikairoyaさんありがとうございます)

#include <stdio.h>

int
main() {
  double a = 123.45;
  int b = 57;
  int c = 89;
  printf("%d %f %d\n", a, b, c);
}

このコードを Windows 32bit OS で -S 出力してみました。

    .file   "aaaa.c"
    .def    ___main;    .scl    2;  .type   32; .endef
    .section .rdata,"dr"
LC1:
    .ascii "%d %f %d\12\0"
    .text
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $48, %esp
    call    ___main
    fldl    LC0
    fstpl   40(%esp)
    movl    $5736(%esp)
    movl    $8932(%esp)
    movl    32(%esp), %eax
    movl    %eax16(%esp)
    movl    36(%esp), %eax
    movl    %eax12(%esp)
    fldl    40(%esp)
    fstpl   4(%esp)
    movl    $LC1, (%esp)
    call    _printf
    leave
    ret
    .section .rdata,"dr"
    .align 8
LC0:
    .long   -858993459
    .long   1079958732
    .ident  "GCC: (i686-posix-sjljbuilt by strawberryperl.com project4.8.2"
    .def    _printf;    .scl    2;  .type   32; .endef

esp レジスタに 57 と 89 の順で格納され、処理順は異なりますが最後にはコードと同じ順に esp レジスタに格納されています。かたや Linux 64bit OS だと

  .file "aaaa.c"
  .section  .rodata
.LC1:
  .string "%d %f %d\n"
  .text
  .globl  main
  .type main, @function
main:
.LFB0:
  .cfi_startproc
  pushq %rbp
  .cfi_def_cfa_offset 16
  .cfi_offset 6, -16
  movq  %rsp, %rbp
  .cfi_def_cfa_register 6
  subq  $32, %rsp
  movabsq $4638387438405602509, %rax
  movq  %rax, -8(%rbp)
  movl  $57, -12(%rbp)
  movl  $89, -16(%rbp)
  movl  -16(%rbp), %edx
  movl  -12(%rbp), %ecx
  movq  -8(%rbp), %rax
  movl  %ecx, %esi
  movq  %rax, -24(%rbp)
  movsd -24(%rbp), %xmm0
  movl  $.LC1, %edi
  movl  $1, %eax
  call  printf
  leave
  .cfi_def_cfa 78
  ret
  .cfi_endproc
.LFE0:
  .size main, .-main
  .ident  "GCC: (GNU4.9.1"
  .section  .note.GNU-stack,"",@progbits

XMM レジスタが使われている様です。printf がフォーマット %f%d を見て取り出すべきレジスタに異なる値が入っているのだから起きて当たり前という訳でした。


2014/08/22


誰も作ってくれないので自分で作った。

mattn/ujihisa - GitHub
https://github.com/mattn/ujihisa

golang で書かれています。go get github.com/mattn/ujihisa/ujm でインストール出来ます。使い方は

$ ujm [filename]

です。ファイル名が無い場合は標準入力から読み取ります。ベースは whitespace です。スペース文字が 便利、タブ文字が 感極まってきました に置き換わります。whitespace に慣れている人ならスイスイと書けるかと思います。例えば

便利便利便利感極まってきました
感極まってきました
便利感極まってきました



この様なコードなら 1 という数字が出力されます。

なお、便利 という文字を出力するにはこれくらいのコードが必要になります。golang で書かれているので Windows でも動きますし、文字の表示でユニコードを使うと Windows でも問題なく表示されます。

とても便利だと思うので、ぜひ使ってみて下さい。


2014/07/31


ShellScript - jq、xmllintコマンドさようなら。俺はパイプが好きだから - Qiita

UNIX哲学の一つとしてよく引用されるマイク・ガンカーズの教義に

  • 1.小さいものは美しい。
  • 2.1つのプログラムには1つのことをうまくやらせよ。

というのがあるが、まずこれができていない

http://qiita.com/richmikan@github/items/e051b5d882c3dd2a39c6

昔の UNIX で扱っていたデータはだいたい行指向でした。そして UNIX は行指向データを扱う為の OS と言っても過言ではありませんでした。 しかし JSON はどうでしょう。JSON は行指向ではありません。ならばツールを変えるのが正しい選択だと思いますし、そこでシェルを選ぶのは正直マニアの方しかいませんよね。 入力データがバイナリであれば、ツールも変えるはずです。JSON もそうすべきだと僕は思います。

理由1. 一つのことをうまくやっていない

ShellScript - jq、xmllintコマンドさようなら。俺はパイプが好きだから - Qiita

JSON だけを扱っていますすし、他のデータを扱ってはいないですよね。

理由2. フィルターとして振る舞うようになりきれてない

ShellScript - jq、xmllintコマンドさようなら。俺はパイプが好きだから - Qiita

jq は JSON の grep 的な存在だと思っています。僕は jq を使いこなしている訳ではないし、「便利だなー」くらいにしか思っていませんが、JSON をシェルで扱いたいと思った場合にはJSON が苦無く扱える言語やツールを選びます

ちなみに jq だと上記の問題はどの様に解決出来るか試してみました。

#!/bin/bash

FILE=$1
cat $FILE | jq -r 'paths|map(if type=="number" then "["+tostring+"]" else "["+tojson+"]" end)|join("")' |\
while read line; do
    value=`cat $FILE | jq -r ".$line|if type==\"string\" or type==\"number\" then \"MATCH:\"+tostring else empty end"`
    if [ "x$value" != "x" ]then
        echo $value sed "s/^MATCH:/.$line\t/"
    fi
done

実行結果は以下の通り。

.["会員名"] 文具 太郎
.["購入品"][0]  はさみ
.["購入品"][1]  ノート(A4,無地)
.["購入品"][2]  シャープペンシル
.["購入品"][3]["取寄商品"]  替え芯
.["購入品"][4]  クリアファイル
.["購入品"][5]["取寄商品"]  6穴パンチ

パスは jq のパス式です。短くていいですね。

ネタだったら、マジレスごめんなさい