2016-05-08

GUI を使わない synergy の設定

Synergy の設定方法についていろんなブログを見ると GUI での設定方法ばかり(例えばこことかこことか)が載っている。 しかし、msvc 2015 で野良ビルドしたものにはGUIツールが作成されない。(mingw32-makeを使えるようにすれば作成されたかもしれないが、面倒なので確認していない) そこで、GUIを使わずに synergy の設定をすることにした。

最初は Ubuntu と Windows でキーボード・マウスを共有させ、次に SSL対応を行なった。

環境は以下の通り
  • サーバー
    • OS: Ubuntu 12.04
    • Synergy: Ubuntu 12.04 のデフォルトの synergy のバージョンは 1.4.10 と古すぎて Windows 用に野良ビルドしたものからは接続できない。 そこでバージョンを合わせるために 2016-05-07 時点の開発中のバージョンをビルドした
  • クライアント

サーバー(ubuntu)側の設定

し synergys を適当なところに置いた。
以下の内容のファイルを $HOME/.synergy.conf に置いた。
section: screens
 ubuntu:
 windows:
end
section: links
 ubuntu:
  right = windows
 windows:
  left = ubuntu
end
section: screens でスクリーンの名前を定義。ここでは ubuntu と windows の2つを使う。
section: links でスクリーンの位置関係を定義。ubuntu の右側は windows で、windows の左側は ubuntu とする。

synergys を起動するときに引数に --name ubuntu を渡して自分のスクリーン名を指定する。
synergys --name ubuntu
($HOME/.synergy.confのスクリーン名 ubuntu をサーバーのホスト名と同じ名前に変更すれば --name ... は不要)

クライアント(Windows)側の設定

c:\synergy に synergyc.exe, synergyd.exe、その他の様々なDLLを入れる。

まず最初にコンソール上でサーバーに接続できるか確認。
c:\synergy\synergyc.exe --name windows サーバーのIPアドレス
(サーバー側の$HOME/.synergy.confのスクリーン名 windows をクライアントのホスト名と同じ名前に変更すれば --name ... は不要)
サーバー側で synergys が動いていてスクリーン名が一致すればこれで動くはず。

この状態だとログインするたびにコンソール上で synergyc を動かす必要がある。また、ログイン画面や、UAC のダイアログ画面ではキーボード・マウスの共有が効かない。 そこでサービスとしてOS起動時に自動起動させる。

コンソールを管理者モードで起動し、以下のコマンドを実行する。
c:\synergy\synergyd.exe /install
synergyd がサービスとして登録される。
次に synergyd が起動するコマンドを指定する。
レジストリエディタを起動し、以下の値を設定する。
  • \\HKEY_LOCAL_MACHINE\Software\Synergy\Command を作成し c:\synergy\synergyc.exe --name windows サーバーのIPアドレスを設定
  • \\HKEY_LOCAL_MACHINE\Software\Synergy\Elevate を作成し、文字列 1 を設定
以下のコマンドでサービスの再起動。
net stop synergy
net start synergy
キーボード・マウスの共有が効いているか確認し、念のため再起動してログイン画面でも効いているかの確認を行った。

SSL対応

デフォルトでは synergy は暗号化なしでクライアントとサーバーの間でデータをやりとりする。パスワードをタイプすると、ネットワーク上を平文のままパスワードが流れる。
また、クライアントがサーバーに接続するとき、認証がまったくない。 例えば、以下の状況のとき他人が勝手にクライアントを操作してしまうかもしれない。
  1. 自宅ではクライアント(スクリーン名 windows)かサーバー(IP: 192.168.0.2)に接続し、サーバーのキーボード・マウスを使ってクライアントを使っている。
  2. クライアントでは自動的に synergy クライアントが起動する。
  3. 自宅の外にクライアントマシンを持って行って、フリーWifi を使う。
  4. フリーWifiのネットワーク上にIP 192.168.0.2 のマシンが偶然あって、synergy サーバーが偶然動いていて、スクリーン名 windows を偶然受け付けるようになっている。
  5. 他人のマシン 192.168.0.2 のキーボード・マウスから自分のクライアントマシンが操作されてしまう。

サーバー(Ubuntu)側の設定

https://wiki.archlinuxjp.org/index.php/Synergy を参考に以下のことを行なった。
  • libns.so~/.synergy/plugins に置く。
  • SSL証明書とフィンガープリントを作成
    mkdir -p ~/.synergy/SSL/Fingerprints
    openssl req -x509 -nodes -days 365 -subj /CN=Synergy -newkey rsa:1024 -keyout ~/.synergy/SSL/Synergy.pem -out ~/.synergy/SSL/Synergy.pem
    openssl x509 -fingerprint -sha1 -noout -in ~/.synergy/SSL/Synergy.pem > ~/.synergy/SSL/Fingerprints/Local.txt
    sed -e "s/.*=//" -i ~/.synergy/SSL/Fingerprints/Local.txt
    
  • synergys を --enable-crypto オプション付きで起動
    synergys --name ubuntu --enable-crypto
    

クライアント(Windows)側の設定

  • ns.dllc:\synergy\plugins にあるのを確認。
  • サーバー側の ~/.synergy/SSL/Fingerprints/Local.txtc:\synergy\SSL\Fingerprints\TrustedServers.txt にコピー。
    mkdir c:\synergy\SSL
    mkdir c:\synergy\SSL\Fingerprints
    copy Local.txt c:\synergy\SSL\Fingerprints\TrustedServers.txt
    
  • レジストリ \\HKEY_LOCAL_MACHINE\Software\Synergy\Command に以下の値を設定
    c:\synergy\synergyc.exe --name windows --profile-dir c: --enable-crypto サーバーのIPアドレス
    
    (--profile-dir の引数に synergy\plugins を追加した場所に ns.dll があり、synergy\SSL\Fingerprints を追加した場所に TrustedServers.txt がある必要がある。)
  • サービスの再起動
    net stop synergy
    net start synergy
    

synergy を msvc 2015 でコンパイル

SynergyGPL の元でリリースされているフリーソフトウェア。 GPLなので自由にソースコードをダウンロードし、自由に改変し、自由に再配布できる。 ただし、開発元からのダウンロードは有料となっている。GPLのソフトウェアとしてはめずらしい。

ここでは、synergy を msvc 2015 でコンパイルした手順を載せる。ただし、GUIでの設定ツールのコンパイルには失敗している。

事前準備

ここを元に以下のソフトウェアをインストールした。 ただし、バージョンは同じではなく、新しめのものを使っている。

コンパイル

MSVC 2015 の x64 用のコンソールを立ち上げ、環境変数を設定するため以下のbatを実行
SET BONJOUR_SDK_HOME=c:\Program Files (x86)\Bonjour SDK

REM Python 2.7.11
PATH=c:\Python27;%PATH%

REM CMake 3.4.1
PATH=c:\Program Files (x86)\CMake\bin;%PATH%

REM Bonjour SDK
PATH=c:\Program Files\Bonjour SDK\Bin;%PATH%

REM WiX Toolset v3.10
PATH=c:\Program Files (x86)\WiX Toolset v3.10\bin;%PATH%

REM Qt 5.6.0 msvc2015_64
PATH=c:\Qt\Qt5.6.0\5.6\msvc2015_64\bin;%PATH%

REM git in cygwin
PATH=%PATH%;c:\cygwin64\bin
次に synergy のソースコードをチェックアウトする。 ただし、本家のソースコード2016-05-07 の時点では msvc 2015 に対応していない。 そこで代わりにこちらの vc2015-qt55 ブランチを使った。(本家のソースコードの msvc 2015 対応が完了したら本家を使うべき)
git clone --depth=1 https://github.com/sheavner/synergy.git
git checkout vc2015-qt55
コンパイル実行。 -g の後ろの数字により MSVC のバージョンを指定している。synergy/ext/toolchain/commands1.py の win32_generators に値と MSVC のバージョンの対応が載っている。 本家が対応したときには msvc 2015 に対応する数字は 12 ではないかもしれない。
hm conf -g 12
hm build
mingw32-make がないというエラーで終了する。多分、ここに載っている Qt SDK 2010.02 の中に minw32-make が入っているのだと思われるが未確認。 この時点までに作成されたものはチェックアウトしたディレクトリの bin/Release の下に入っている。 インストーラやGUIでの設定ツールは作成されていないが、コマンドラインから使えるツールは作成されていたので、ここでコンパイルは完了とする。

インストール

bin/Release の下のファイルをまるごと別のディレクトリにコピー。

これで完了。
GUIを使わない設定の方法はここに記載した。

2015-01-12

Undocumented OCI Handle Attributes

OCI_ATTR_RECEIVE_TIMEOUT and OCI_ATTR_SEND_TIMEOUT are defined in oci.h but not documented in Oracle Call Interface Programmer's Guide. I guess their specifications as follows.

Server Handle Attributes

OCI_ATTR_RECEIVE_TIMEOUT

   Mode
   READ/WRITE

   Description
   Specify the receive timeout in milliseconds.
   Specifying zero means no timeout.

   Attribute Data Type
   ub4 */ub4

OCI_ATTR_SEND_TIMEOUT

   Mode
   READ/WRITE

   Description
   Specify the send timeout in milliseconds.
   Specifying zero means no timeout.

   Attribute Data Type
   ub4 */ub4

I checked OCI_ATTR_RECEIVE_TIMEOUT on Linux with various Oracle versions.
Oracle 10.2 and earlier
"ORA-24315: illegal attribute type" on setting the attribute
Oracle 11.1
"ORA-03113: end-of-file on communication channel" on timeout
Oracle 11.2 and later
"ORA-12609: TNS: Receive timeout occurred" on timeout

2015-01-02

gcc で _BitScanForward & _BitScanReverse 互換関数、MSVC で __builtin_clz & __builtin_ctz 互換関数

gcc には __builtin_clz, __builtin_ctz という組み込み関数があり、MSVC(Microsoft Visual C++) には _BitScanForward, _BitScanReverse という組み込み関数がある。 とあるアプリを Windows でコンパイルしようとしたところ、gcc の場合は __builtin_clz, __builtin_ctz を使っているところがあった。MSVC にも同じような関数があったよなと思い、調べたことを以下の順番で書く。
  • __builtin_clz, __builtin_ctz, _BitScanForward, _BitScanReverse の仕様
  • MSVC の _BitScanForward, _BitScanReverse を使った __builtin_clz, __builtin_ctz 互換関数
  • gcc の __builtin_clz, __builtin_ctz を使った _BitScanForward, _BitScanReverse 互換関数
  • ついでに、gcc のインラインアセンブラを使った _BitScanForward, _BitScanReverse 互換関数

__builtin_clz, __builtin_ctz, _BitScanForward, _BitScanReverse の仕様

  • int __builtin_clz(unsigned int)
gccの組み込み関数。 clz は Count Leading Zeros の略で、上位ビットの0の個数を返す。 例えば引数の値が二進数で 00000000 10110011 11111100 11010100 のとき、上位ビットの連続する 0 の数は8個なので、戻り値は 8 となる。 全ビットが0のときの戻り値は未定義。
  • int __builtin_ctz(unsigned int)
gccの組み込み関数。 ctz は Count Trailing Zeros の略で、下位ビットの0の個数を返す。 例えば引数の値が二進数で 00000000 10110011 11111100 11010100 のとき、下位ビットの連続する 0 の数は2個なので、戻り値は 2 となる。 全ビットが0のときの戻り値は未定義。
  • unsigned char _BitScanForward(unsigned long *Index, unsigned long Mask)
MSVCの組み込み関数。 下位ビットから値が1のビットを検索し、最初に見つかった値1のビットの位置を *Index に設定する。全ビットが0のときの戻り値は0で、それ以外の場合は非0値。 例えば引数 Mask の値が二進数で 00000000 10110011 11111100 11010100 のとき、下位ビットから見て最初の1の位置は2(最下位ビットの位置は0)なので、*Index に2を設定したあと、非0の値が戻り値となる。
下位から見て最初の1が見つかるまでのビットはすべて0なので、最初の1の位置は、下位の連続する0の個数と同じ値となる。したがって、__builtin_ctz を使えば _BitScanForward が実装できるし、_BitScanForward を使えば、__builtin_ctz を実装できる。
  • unsigned char _BitScanReverse(unsigned long *Index, unsigned long Mask)
MSVCの組み込み関数。 上位ビットから値が1のビットを検索し、最初に見つかった値1のビットの位置を *Index に設定する。全ビットが0のときの戻り値は0で、それ以外の場合は非0値。 例えば引数 Mask の値が二進数で 00000000 10110011 11111100 11010100 のとき、上位ビットから見て最初の1の位置は23(最下位ビットの位置は0)なので、*Index に23を設定したあと、非0の値が戻り値となる。
上位から見て最初の1が見つかるまでのビットはすべて0なので、最初の1の位置は上位の連続する0の個数から簡単な計算で求まる。したがって、__builtin_clz を使えば _BitScanReverse が実装できるし、_BitScanReverse を使えば、__builtin_clz を実装できる。

MSVC の _BitScanForward, _BitScanReverse を使った __builtin_clz, __builtin_ctz 互換関数

#include <intrin.h>

int __builtin_clz(unsigned int n)
{
    unsinged long index;
    /* n が0のときの __builtin_clz の戻り値は未定義なので、
     * _BitScanReverse の戻り値チェックは割愛する。
     */
    _BitScanReverse(&index, n);
    return 31 - index;
}

int __builtin_ctz(unsigned int n)
{
    unsinged long index;
    /* n が0のときの __builtin_ctz の戻り値は未定義なので、
     * _BitScanForward の戻り値チェックは割愛する。
     */
    _BitScanForward(&index, n);
    return index;
}

gcc の __builtin_clz, __builtin_ctz を使った _BitScanForward, _BitScanReverse 互換関数

unsigned char inline _BitScanForward(unsigned int *Index, unsigned int Mask)
{
    if (Mask) {
        *Index = __builtin_ctz(Mask);
        return 1;
    } else {
        /* 戻り値が0のとき、*Index がどうなるかは未定義。*/
        return 0;
    }
}

unsigned char inline _BitScanForward(unsigned int *Index, unsigned int Mask)
{
    if (Mask) {
        *Index = 31 - __builtin_clz(Mask);
        return 1;
    } else {
        /* 戻り値が0のとき、*Index がどうなるかは未定義。*/
        return 0;
    }
}

gcc のインラインアセンブラを使った _BitScanForward, _BitScanReverse 互換関数

unsigned char inline _BitScanForward(unsigned int *Index, unsigned int Mask)
{
    unsigned char rv;
    __asm(
          "bsfl %2, %0;"
          "setne %1;"
          : "=r"(*Index),"=r"(rv)
          : "g"(Mask)
          : "cc");
    return rv;
}

unsigned char inline _BitScanReverse(unsigned int *Index, unsigned int Mask)
{
    unsigned char rv;
    __asm(
          "bsrl %2, %0;"
          "setne %1;"
          : "=r"(*Index),"=r"(rv)
          : "g"(Mask)
          : "cc");
    return rv;
}

2012-09-07

C言語でのOracle外部プロシージャ

Oracleの外部プロシージャとは、簡単に言うと、PL/SQL以外の言語で書かれたストアドプロシージャのことを指す。 Oracle本家でサポートしているのは以下の3つ。 サードパーティーでは perl が使える。 Oracle® Databaseアドバンスト・アプリケーション開発者ガイドを参照しながらC言語で簡単な外部プロシージャを作ってみる。
まず、system ユーザーでログインして試験用ユーザーを作成
~$ sqlplus

SQL*Plus: Release 10.2.0.1.0 - Production on Fri Sep 7 13:22:00 2012

Copyright (c) 1982, 2005, Oracle.  All rights reserved.

Enter user-name: system/manager

Connected to:
Oracle Database 10g Express Edition Release 10.2.0.1.0 - Production

SQL> create user extproc_test identified by extproc_test                    
  2  default tablespace users temporary tablespace temp;

User created.

SQL> grant connect, resource, create library to extproc_test;

Grant succeeded.

次に試験用ユーザーでログインして、共有ライブラリの場所を指定。
SQL> connect extproc_test/extproc_test 
Connected.
SQL> create library extproc_test as '${ORACLE_HOME}/lib/extproc_test.so';
  2  /

Library created.

$ORACLE_HOME/lib, $ORACLE_HOME/bin 以外の場所に共有ライブラリを置くときは 環境変数EXTPROC_DLLSを設定すること。
次にSQL関数を作成。
SQL> create function add_func(x pls_integer, y pls_integer)                       
  2  return pls_integer
  3  as language C library extproc_test name "add_func";
  4  /

Function created.

次にC言語の関数を作成。
int add_func(int x, int y)
{
  return x + y;
}
pls_integer はデフォルトでは C 言語の int にマッピングされる。
コンパイルして $ORACLE_HOME/lib にコピー。
$ gcc -shared -o extproc_test.so extproc_test.c
$ cp extproc_test.so $ORACLE_HOME/lib
実行してみる。
SQL> select add_func(1, 2) from dual;

ADD_FUNC(1,2)
-------------
            3
期待通りに動いた。
試しにNULLを渡してみる。
SQL> select add_func(1, NULL) from dual;
select add_func(1, NULL) from dual
                              *
ERROR at line 1:
ORA-01405: fetched column value is NULL
C言語では NULL かどうかの判断ができないから当然か。。。
C言語側にNULLかどうかのフラグを渡せるよう関数定義を修正する。
SQL> create or replace function add_func(x pls_integer, y pls_integer)
  2  return pls_integer
  3  as language c library extproc_test name "add_func"
  4  parameters(x, x indicator, y, y indicator, return indicator);
  5  /

Function created.

parameters の指定に従い、C言語の関数定義も変更する。
#include 

int add_func(int x, short x_ind, int y, short y_ind, short *rind)
{
  if (x_ind || y_ind) { /* x または y が NULL のとき */
    *rind = OCI_IND_NULL; /* 戻り値は NULL */
    return 0;
  }
  *rind = 0; /* 戻り値は非NULL */
  return x + y; /* 戻り値 */
}
再度コンパイルしてコピー。
$ gcc -shared -o extproc_test.so extproc_test.c -I$ORACLE_HOME/rdbms/public
$ cp extproc_test.so $ORACLE_HOME/lib
実行してみる。
SQL> select add_func(1, NULL) from dual;
select add_func(1, NULL) from dual
                              *
ERROR at line 1:
ORA-28576: lost RPC connection to external procedure agent
使用中の共有ライブラリを上書きしたせいか、それともメモリ上には古い共有ライブラリが 載っているのにOracle側の関数定義を変えたせいか。 まあ、どちらにしても、これでextprocプロセスが再起動してくれれば次はうまく行くはず。
SQL> select add_func(1, NULL) from dual;

ADD_FUNC(1,NULL)
----------------

今度は動いた。
念のため非NULL値の試験も行う。
SQL> select add_func(1, 2) from dual;

ADD_FUNC(1,2)
-------------
            3
動いた。

以上。

2012-07-21

SSE4.2 の crc32c 命令の呼び出し

SSE4.2 に追加された crc32c 用の機械語命令を使うのに必要そうな機能をメモ書き。 ちなみに、SSE4.2 対応の CPU を持ってないので、未検証の項目多い。

SSE4.2対応のCPU

  • Intel系はNehalemマイクロアーキテクチャ以降
  • AMD系はBulldozerマイクロアーキテクチャ以降
Wikipedia によるとNehalemマイクロアーキテクチャは「 主に2008年〜2011年ごろに発売された。」と書いてあるので、最近のデスクトップ用のCPUならば大丈夫?

SSE4.2対応のCPUかどうかのチェック

(2012-11-03 大幅書き換え)

当然だが、SSE4.2 に対応してないCPUでは crc32c 機械語命令は使えないので、事前に SSE4.2 に対応しているかどうかをチェックする必要がある。
以下の関数 sse4_2_is_supported() を呼び出して、戻り値が 1 ならばSSE4.2対応、0の場合は非対応。
#define CPUID_ECX_BIT_SSE4_2 (1u << 20)

/* GNU C Compiler */
#if defined __GNUC__
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)
#include <cpuid.h>
/* gcc version >= 4.3 */
int sse4_2_is_supported(void)
{
  unsigned int eax, ebx, ecx, edx;
  __cpuid(1, eax, ebx, ecx, edx);
  return (ecx & CPUID_ECX_BIT_SSE4_2) ? 1 : 0;
}
#else
/* gcc version < 4.3 */
int sse4_2_is_supported(void)
{
  unsigned int ecx;
#if defined(__i386__) && defined(__PIC__)
  __asm("movl $1, %%eax;"
        "pushl %%ebx;"
        "cpuid;"
        "popl %%ebx;"
        : "=c" (ecx)
        :
        : "eax", "edx");
#else
  __asm("movl $1, %%eax;"
        "cpuid;"
        : "=c" (ecx)
        :
        : "eax", "ebx", "edx");
#endif
  return (ecx & CPUID_ECX_BIT_SSE4_2) ? 1 : 0;
}
#endif

/* Microsoft Visual C++ */
#elif defined _MSC_VER
#if _MSC_VER >= 1400
#include <intrin.h>
/* msvc version >= 2005 */
int sse4_2_is_supported(void)
{
  int cpuinfo[4];
  __cpuid(cpuinfo, 1);
  return (cpuinfo[2] & CPUID_ECX_BIT_SSE4_2) ? 1 : 0;
}
#else
/* msvc version < 2005 */
int sse4_2_is_supported(void)
{
  unsigned int c;
  __asm {
    mov eax, 1
    cpuid
    mov c, ecx
  }
  return (c & CPUID_ECX_BIT_SSE4_2) ? 1 : 0;
}
#endif

#else
#error unsupported compiler
#endif

crc32c命令の呼び出し

(2012-11-03 大幅書き換え)

gcc 4.3以上、icc 12.0 以上、Visual C++ 2008 以上ならば、 以下のIntrinsicsが使える。
  • unsigned int _mm_crc32_u8 (unsigned int crc, unsigned char v);
  • unsigned int _mm_crc32_u16 (unsigned int crc, unsigned short v);
  • unsigned int _mm_crc32_u32 (unsigned int crc, unsigned int v);
  • unsigned long long _mm_crc32_u64 (unsigned long long crc, unsigned long long v);(Linux)
  • unsigned __int64 _mm_crc32_u64 (unsigned __int64 crc, unsigned __int64 v);(Windows)
SSE4.2 対応のCPUを持ってないので、実機での試験はしてないが、以下のコードでOKなのではないかと思う。
#include <nmmintrin.h>

unsigned int crc32c_sse4_2(unsigned int crc32c, const char *buffer, size_t length)
{
  size_t quotient;

#if 0 /* 未検証だがアライメントを揃える必要はなさそう。 */
  /* buffer の先頭部分のアライメントに沿ってない部分の処理 */
  if ((((size_t)buffer) & 1) && length >= 1) {
    crc32c = _mm_crc32_u8(crc32c, *(unsigned char*)buffer);
    buffer += 1;
    length -= 1;
  }
  if ((((size_t)buffer) & 2) && length >= 2) {
      crc32c = _mm_crc32_u16(crc32c, *(unsigned short*)buffer);
      buffer += 2;
      length -= 2;
    }
#if defined(__x86_64) || defined(_M_X64)
  if ((((size_t)buffer) & 4) && length >= 4) {
    crc32c = _mm_crc32_u32(crc32c, *(unsigned int*)buffer);
    buffer += 4;
    length -= 4;
  }
#endif
#endif

  /* アライメントに沿っている部分の処理 */
  quotient = length / sizeof(size_t);
  while (quotient--) {
#if defined(__x86_64) || defined(_M_X64)
    crc32c = _mm_crc32_u64(crc32c, *(size_t*)buffer);
#else
    crc32c = _mm_crc32_u32(crc32c, *(size_t*)buffer);
#endif
    buffer += sizeof(size_t);
  }

  /* buffer の後ろの部分のアライメントに沿ってない部分の処理 */
#if defined(__x86_64) || defined(_M_X64)
  if (length & 4) {
    crc32c = _mm_crc32_u32(crc32c, *(unsigned int*)buffer);
    buffer += 4;
  }
#endif
  if (length & 2) {
    crc32c = _mm_crc32_u16(crc32c, *(unsigned short*)buffer);
    buffer += 2;
  }
  if (length & 1) {
    crc32c = _mm_crc32_u8(crc32c, *(unsigned char*)buffer);
  }
  return crc32c;
}
アライメントを気にした処理をしているが、アライメントを揃える必要が本当にあるかは不明...。Intel の Optimization Reference マニュアルの CRC32 のサンプルはアライメントを気にしてないので、アライメントを揃える必要はなさそう。
ちなみに gcc の場合コンパイルオプションに -msse4.2 を付ける必要がある。

SSE4.2の有無により呼び出す関数を分ける方法

例えば SSE4.2 があるときはcrc32c_sse4_2()を呼び出して、SSE4.2 がないときは C で実装した関数crc32c_implemented_by_c()を呼び出したい場合、以下のように関数ポインタを使用することが多い。
unsigned int (*crc32c)(unsigned int crc32, const char *p, size_t len);

int main()
{
    ...初期化時に関数ポインタ crc32c に実際に呼び出す関数を設定 ...
    if (sse4_2_is_supported()) {
        crc32c = crc32c_sse4_2;
    } else {
        crc32c = crc32c_implemented_by_c;
    }

    ...
    crc = crc32c(crc, buf, buflen);  // crc32c関数の呼び出し
}

最近の gcc ならば、 ifunc というアトリビュートがあり、これを使うとプログラムのロード時にどの関数を呼び 出すかをチェックできる。
// この関数はプログラムのロード時、constructor の呼び出し前に実行される。
unsigned int (*resolve_crc32c(void))(unsigned int crc32, const char *p, size_t len)
{
    if (sse4_2_is_supported()) {
        return crc32c_sse4_2;
    } else {
        return crc32c_implemented_by_c;
    }
}

unsigned int crc32c(unsigned int crc32, const char *p, size_t len)  __attribute__ ((ifunc ("resolve_crc32c")));

int main()
{
    ...
    crc = crc32c(crc, buf, buflen);  // crc32c関数の呼び出し
}
で、出力されたアセンブリを見てみると、、、crc32c のような重い関数の場合、関数内部の負荷と比べると、関数呼び出し部分の微細な最適化はあまり意味がないような気がしてきた。