関数へのポインターの出力(回答編)

question:1185077855

回答が 1 件しか付かなくて残念。しかし、コメント欄への書き込みも含めて、printf 系関数で変換指定子"%p"を使う方法しか出なかったので、やはり関数へのポインターを void へのポインターへ常に変換できると思っている人は多いのではないかと思われる。

CプログラミングFAQ―Cプログラミングのよく尋ねられる質問 (アジソン ウェスレイ・トッパン 情報化学シリーズ)

4.13
質問: 包括的な汎用ポインタ型は何か。関数へのポインタを void * に突っ込んだらコンパイラにしかられた。
回答: “包括的な汎用ポインタ型”は存在しない。void * に保障されていることはオブジェクト(すなわちデータ)を指すポインタを格納できることだけだ。関数ポインタを void * に変換することは移植性が高くない。(マシンによっては関数ポインタは非常に大きい。どのデータポインタよりも大きい。)

まあ、今時のマシンなら実際変換できる処理系は多いのだろうけど、変換できない場合にも使える方法(プログラム)を考えてみた。
ただし、次の 2 点の制限がある。

  • CHAR_BIT が 4 の倍数の場合にしか対応していない
  • 複雑なエンディアンには対応していない*1
fptostr.c
#include <limits.h>
#include <string.h>
#include <stdio.h>

#if (CHAR_BIT & 3) != 0
#  error error: fptostr requires (CHAR_BIT & 3) == 0 to be compiled
#endif

#define FUNCPTR_DIGITINBYTE (CHAR_BIT>>2)
typedef int (*funcptr_t)();
#define FUNCPTR_STRSIZE (2+sizeof(funcptr_t)*FUNCPTR_DIGITINBYTE)
#define FUNCPTR_BYTETOSTR(cary,ci,c,di,sp) \
    do { \
        c = cary[ci]; \
        for ( di = 0; di < FUNCPTR_DIGITINBYTE; ++di ) { \
            *sp = xdigits[c & 0xf]; \
            c >>= 4; \
            --sp; \
        } \
    } \
    while ( 0 )

int fptostr(funcptr_t p, char str[], size_t len)
{
    static const char *xdigits = "0123456789abcdef";
    const int bom = 1;
    const int is_little_endian = *(const char *)&bom == 1;
    unsigned char cary[sizeof(p)];
    unsigned char c;
    char *sp;
    int ci, di;

    if ( !str || len <= FUNCPTR_STRSIZE ) return -1;

    memcpy(cary, &p, sizeof(p));
    sp = str;
    *sp = '0'; ++sp; *sp = 'x';
    sp = str + FUNCPTR_STRSIZE;
    *sp = '\0';
    --sp;
    if ( is_little_endian ) {
        for ( ci = 0; ci < sizeof(p); ++ci ) {
            FUNCPTR_BYTETOSTR(cary, ci, c, di, sp);
        }
    }
    else {
        for ( ci = sizeof(p)-1; ci >= 0; --ci ) {
            FUNCPTR_BYTETOSTR(cary, ci, c, di, sp);
        }
    }

    return 0;
}

int main()
{
    char fpstr[FUNCPTR_STRSIZE+1];
    static const funcptr_t fps[] = {
        (funcptr_t)main
        , (funcptr_t)printf
        , (funcptr_t)fptostr
        , NULL
    };
    int i;

    printf("FUNCPTR_DIGITINBYTE = %d\n", FUNCPTR_DIGITINBYTE);
    printf("FUNCPTR_STRSIZE     = %d\n", FUNCPTR_STRSIZE);
    for ( i = 0; fps[i]; ++i ) {
        printf("----\n");
        printf("%%p      => %p\n", (const void *)fps[i]);
        if ( fptostr(fps[i], fpstr, sizeof(fpstr)) != 0 ) return 1;
        printf("fptostr => %s\n", fpstr);
    }

    return 0;
}
実行例(i386/FreeBSD 6.2/gcc 3.4.6)
FUNCPTR_DIGITINBYTE = 2
FUNCPTR_STRSIZE     = 10
----
%p      => 0x8048660
fptostr => 0x08048660
----
%p      => 0x80483b0
fptostr => 0x080483b0
----
%p      => 0x804852c
fptostr => 0x0804852c
実行例(i686/CYGWIN_NT-5.1/gcc 3.4.4)
FUNCPTR_DIGITINBYTE = 2
FUNCPTR_STRSIZE     = 10
----
%p      => 0x40118b
fptostr => 0x0040118b
----
%p      => 0x401310
fptostr => 0x00401310
----
%p      => 0x401050
fptostr => 0x00401050
実行例(i686/Windows XP/Visual Studio 2005)
FUNCPTR_DIGITINBYTE = 2
FUNCPTR_STRSIZE     = 10
----
%p      => 00401160
fptostr => 0x00401160
----
%p      => 00401219
fptostr => 0x00401219
----
%p      => 00401000
fptostr => 0x00401000

*1:ミドルエンディアンってどういう利点があるんだろう…?