C++の可変長引数(va_list)の使い方

March 1,2017 12:03 AM
Category:[C/C++]
Tag:[C/C++]
Permalink

C++の可変長引数(va_list)の使い方について紹介します。

1.可変長引数とは

可変長引数は、関数宣言および関数定義の最後の引数が省略記号(...)である場合、可変数の引数を受け取ることができるものです。

Func( int i, ... );

上記のように、省略記号が最後の引数で、省略記号(...)の前にカンマにある場合に限り、省略記号が可変長引数となります。

2.サンプル1

下記のサンプルは、関数「hoge」起動時の第1パラメータに可変長引数の総数「5」、第2パラメータ以降(=可変長引数)に「H」「e」「l」「l」「o」を渡し、表示させるものです。

#include <stdio.h>
#include <stdarg.h>
 
void hoge( int num, ...) {
    va_list args;
    va_start( args, num );
    for( int i = 0; i < num; ++i ) {
        printf( "%c\n", va_arg( args, char* ) );
    }
    va_end( args );
}
 
int main () {
   hoge( 5, 'H', 'e', 'l', 'l', 'o' );
}

各マクロの意味は次のとおりです。

va_start

可変長引数リストを初期化します。

  • 第1パラメータ:引数リスト
  • 第2パラメータ:指定した引数以降を引数リストに格納

サンプルでは、

va_start( args, num );

となっているので、引数リストargsに、num以降の引数を格納します。

va_arg

引数リストから順番に値を取得します。

  • 第1パラメータ:引数リスト
  • 第2パラメータ:取得時の型

サンプルでは、

va_arg( args, char* )

となっているので、引数リストargsからchar*型で値を取得します。

va_end

引数リストをクリアします。

  • 第1パラメータ:引数リスト

引数リストの操作が終わった後、この処理が必要なようです。

可変長引数マクロでは引数の個数を判断できないため、関数呼び出し時に引数の数を判断できる情報を第1パラメータで渡すのが定石のようです。

前述のサンプルでは、第1パラメータで指定された値をfor文の繰り返し回数に用いて、引数を取得・表示します。

    for( int i = 0; i < num; ++i ) {
        printf( "%c\n", va_arg( args, char* ) );
    }

3.サンプル2

前述のサンプルでは一意の型しか扱えないため、実践で使えそうなサンプルを作ってみました。

#include <stdio.h>
#include <stdarg.h>
 
void hoge( char *types, ...) {
    va_list args;
    va_start( args, types );
    for( int i = 0; types[i] != '\0'; ++i ) {
        printf( "%c:", types[i] );
        switch( types[i] ) {
        case 'i':
            printf( "%i\n", va_arg( args, int ) );
            break;
         case 'f':
            printf( "%f\n", va_arg( args, double ) );
            break;
         case 'c':
             printf( "%c\n", va_arg( args, char* ) );
             break;
         case 's':
             printf( "%s\n", va_arg( args, char* ) );
             break;
         default:
             break;
        }
    }
    va_end( args );
}
 
int main () {
   hoge( "ifcs", 30, 15.7f, 'a', "Hello World!" );
}

このサンプルでは、引数の数および型を第1パラメータで渡し、型によってswitch文で分岐させるようにしています。

4.参考サイト

参考サイトは下記です。ありがとうございました。

Comments [0] | Trackbacks [0]

C/C++のオブジェクトファイルをldコマンドでリンクする方法

February 24,2017 12:33 AM
Category:[C/C++]
Tag:[C/C++]
Permalink

C/C++のオブジェクトファイルをldコマンドでリンクする方法を紹介します。

1.問題点

下記のプログラムを作りました。

sample.h

class Sample {
    public:
        Sample();
        void foo(int);
};

sample.cc

#include "stdio.h"
#include "sample.h"
 
Sample::Sample(){
}
 
void Sample::foo() {
    printf( "Hello, World!\n" );
}

test.h

class Test {
    public:
        Test();
        void hoge();
};

test.cc

#include "test.h"
#include "sample.h"
 
Test::Test(){
}
 
void Test::hoge() {
    Sample a;
    a.foo();
}
 
int main () {
   Test test;
   test.hoge();
}

これをコンパイルして、オブジェクトファイルsample.o、test.oを作ります。

% g++ -I. -c sample.cc test.cc

このあと、g++ではなくldコマンドでリンケージを実行すると、「`puts' に対する定義されていない参照です」というエラーになります。

% ld -o main sample.o test.o
ld: 警告: エントリシンボル _start が見つかりません。デフォルトとして 00000000004000b0 を使用します
sample.o: 関数 `Sample::foo()' 内:
sample.cc:(.text+0x1c): `puts' に対する定義されていない参照です

ちなみに、g++コマンドでは正常にリンクできます。

% g++ -o main sample.o test.o

色々調べてみて"-lc"および"-lm"オプションを追加してみたところ、下記のコマンドラインで実行ファイル作成までできました。

% ld -o main -lc -lm sample.o test.o

ただ、実行すると「-bash: ./main: /lib/ld64.so.1: bad ELF interpreter: そのようなファイルやディレクトリはありません」というエラーになってしまいます。

% ./main
-bash: ./main: /lib/ld64.so.1: bad ELF interpreter: そのようなファイルやディレクトリはありません

lddコマンドで使われているライブラリを調べてみました。

g++でリンクした実行ファイル

% ldd main
        linux-vdso.so.1 =>  (0x00007fff19ba3000)
        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f21983a5000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f21980a3000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f2197e8c000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f2197acb000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f21986c2000)

ldでリンクした実行ファイル

% ldd main
        linux-vdso.so.1 =>  (0x00007ffd1fc4f000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f6f19b6f000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f6f1986d000)
        /lib/ld64.so.1 => /lib64/ld-linux-x86-64.so.2 (0x00007f6f19f45000)

出力結果より、オプションで指定するライブラリが足りないようですが、具体的な指定方法が分かりません。

ということで、ldコマンドでリンクする方法を紹介します。

2.ldコマンドでリンクする

ldコマンドでリンクするには、一旦g++コマンドに"-v"オプションをつけてリンクします。

% g++ -v -o main sample.o test.o
組み込み spec を使用しています。
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper
ターゲット: x86_64-redhat-linux
configure 設定: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,java,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux
スレッドモデル: posix
gcc バージョン 4.8.5 20150623 (Red Hat 4.8.5-4) (GCC)
COMPILER_PATH=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:/usr/libexec/gcc/x86_64-redhat-linux/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/
LIBRARY_PATH=/usr/lib/gcc/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/:/lib/../lib64/:/usr/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-o' 'main' '-shared-libgcc' '-mtune=generic' '-march=x86-64'
 /usr/libexec/gcc/x86_64-redhat-linux/4.8.5/collect2 --build-id --no-add-needed --eh-frame-hdr --hash-style=gnu -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o main /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtbegin.o -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5 -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../.. sample.o test.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtend.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crtn.o

"-v"は、コンパイルの各ステージで実行されるコマンドを表示するオプションです。

で、上記の最後の行に表示された"/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/collect2"に--helpをつけて実行すると、

% /usr/libexec/gcc/x86_64-redhat-linux/4.8.5/collect2 --help
Usage: collect2 [options]
 Wrap linker and generate constructor code if needed.
 Options:
  -debug          Enable debug output
  --help          Display this information
  -v, --version   Display this program's version number
 
Overview: http://gcc.gnu.org/onlinedocs/gccint/Collect2.html
Report bugs: <http://bugzilla.redhat.com/bugzilla>
 
使用法: /usr/bin/ld [options] file...
(後略)

どうやらこれがldコマンドらしいので、これに続く内容(「--build-id」以降)をまるっとコピーして、ldコマンドのオプションに利用します。

% ld -o main --build-id --no-add-needed --eh-frame-hdr --hash-style=gnu -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o main /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtbegin.o -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5 -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 -L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../.. sample.o test.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtend.o /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crtn.o

これで正常な実行ファイルが作成できました。

3.参考サイト

参考サイトは下記です。ありがとうございました。

Comments [0] | Trackbacks [0]

C++のswitch文で「crosses initialization of~」というエラーの対処

February 23,2017 12:03 AM
Category:[C/C++]
Tag:[C/C++]
Permalink

C++のswitch文で「crosses initialization of」というエラーの対処方法について紹介します。

1.問題点

下記のサンプルプログラムを作りました。

sample.h

class Sample {
    public:
        Sample();
        void foo();
};

sample.cc

#include "stdio.h"
#include "sample.h"
 
Sample::Sample(){
}
 
void Sample::foo() {
    printf( "Hello World!\n" );
}

test.h

class Test {
    public:
        Test();
        void hoge(int);
};

test.cc

#include "test.h"
#include "sample.h"
 
Test::Test(){
}
 
void Test::hoge(int result) {
    switch (result) {
        case 0:
            break;
        case 1:
            Sample a;
            a.foo();
            break;
        default:
            break;
    }
}
 
int main () {
   Test test;
   test.hoge(1);
}

このプログラムをコンパイルすると、下記の「crosses initialization of」というエラーに遭遇します。

% g++ -g -w -ansi -fpermissive -I. test.cc sample.c
test.cc: メンバ関数 'void Test::hoge(int)' 内:
test.cc:13:20: エラー:   crosses initialization of 'Sample a'
             Sample a;
                    ^

2.原因

まず、switch文はcase内ではなく、switch全体でひとつのスコープとみなされます。

よって"case 1:"に実装された変数aのスコープはswitch文全体で有効になりますが、"case 1:"が実行されなかった場合、aの定義が行われないため、コンパイルエラーとなるようです。

今回はクラス定義が対象の処理でしたが、単なる変数定義でも同様のエラーが発生します。

3.対処

"case 1"全体をブロックで囲みます(括り方はさまざまですが少なくとも変数aがカッコで括ったブロック内にあること)。

void Test::hoge(int result) {
    switch (result) {
        case 0:
            break;
        case 1: {
            Sample a;
            a.foo();
            break;
        }
        default:
            break;
    }
}

これで変数のスコープがブロック内に閉じられます。

4.参考サイト

参考サイトは下記です。ありがとうございました。

Comments [0] | Trackbacks [0]
 1  |  2  |  3  |  4  |  5  | All pages