【マイグレーションTips】Cのプログラム移植/コンパイルエラーの原因と対処方法(HP-UX→Linux)
はじめに
皆さん、こんにちは。株式会社SHIFT 技術統括部 マイグレーションGの村田と申します。主にお客様が使用するコンピュータシステムのマイグレーション作業支援を担当しています。
SHIFT技術ブログでは様々な技術情報が投稿されていますが、私からは数々のマイグレーション作業に関わった経験より、今後マイグレーション作業を行う技術者に向けて役に立つと考えられる情報を選んで紹介してまいります。
マイグレーションって?
本題に入る前に、そもそもマイグレーションとは何でしょうか。
IT用語辞典では、以下のとおり定義されています。
コンピュータを使った情報処理技術の導入により、企業内の事務処理や社会での様々な活動が電子化されました。
コンピュータシステムは一度導入すると永遠に動くようなイメージがありますが、残念なから実際はそうではありません。
コンピュータシステムを構成するサーバーやネットワークなどのハードウェアは家電製品と同じように年数が経過すると故障しやすくなったり、保守サポートが終了するために新しい製品への置き換えが必要になります。
またOSやDBMSなどのソフトウェアも高性能な後継製品の登場により古い製品のサポートが打ち切られる事態が発生します(わかりやすい事例だとWindowsXPやWindows7,8.1のサポート終了など)。
単純にそのまま新しい環境に載せ換えることができれば問題ありませんが、OSやDBMS独自の仕様などが使えない等の理由でシステムの改修要となるケースが多数あり、問題解決等に費やす時間を含めてシステム移行作業の工数を大きく圧迫することがあります。
株式会社SHIFTではマイグレーションで起きうるこのような事態に対処するための専門技術者を揃えて、お客様のコンピュータシステムのマイグレーション作業を円滑に進めるためのお手伝いをさせて頂いております。
【現象】CのプログラムをHP-UXからLinuxへ移植したら、コンパイルエラーが発生
さて、最初にご紹介するマイグレーションTipsは、C言語のプログラムを
移植する時に遭遇した問題への解決方法です。
システム:HP-UX→RedHat Linuxへのマイグレーション
言語:C言語
事象:HP-UXで稼働中のプログラムソースをRedHat Linuxでコンパイルするとエラー(Undefined Symbol)が発生する
C言語はUNIXの開発と共にできた言語ですので、差異が大きいシステムコールの部分を除けば、他のUNIXプラットフォームへの移植は比較的易しい言語です。しかしUNIXプラットフォームの特性により、同じコードを書いても一部のプラットフォームではエラーになることがあります。
この点がマイグレーションにおいては問題となります。
今回の事例について、簡単なサンプルコードで説明しましょう。
-- main.c --
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
int rtn = 0;
rtn = funcA(argv[1]); /* 共有ライブラリの関数コール */
if (rtn == 0) {
printf("OK\n");
else
printf("NG\n");
}
exit(rtn);
}
-- func.c -- ※共有ライブラリ(動的ライブラリ)に格納
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PARAMS1 "s1"
#define PARAMS2 "s2"
extern int ext_cd;
int funcA(char *arg)
{
int sub_cd = 0;
if (strcmp(arg,PARAMS1) != 0)
{
sub_cd = 999;
printd("NG:%d\n", ext_cd);
}
return(sub_cd);
}
メインモジュールからfuncA関数を呼び出す構造のプログラムです。funcA関数は共有ライブラリに格納しています。
このソースをコンパイルすると、UNIXプラットフォームにより以下のような違いが現れます。
HP-UX
コンパイル成功
Linux
コンパイルエラー(ext_cdがUndefined Symbol(シンボル未解決))
HP-UXでは動いていたプログラムが、Linuxではコンパイルでエラーとなってしまいました…
【原因】リンケージエディタ(ld)の動作がUNIXプラットフォームにより異なる
この違いは、コンパイラが出力した中間オブジェクトを解析して実行モジュールにまとめるリンケージエディタ(ld)の動作がOS毎に違うことにより現れます。
HP-UXのリンケージエディタ
extern宣言された外部シンボルの実体が中間オブジェクト上にあることを厳格にチェックしません
Linuxのリンケージエディタ
extern宣言された外部シンボルも含めてすべてのシンボルの実体が中間オブジェクトにあることを厳格にチェックします
HP-UXでは「エラー箇所があっても動けばいい」という寛容さがあり、Linuxでは「エラーはないことが当たり前」という考え方の違いが表れているように考えられます。
サンプルコードのプログラムは極端なソースコードですので、C言語の知識がある方なら、このプログラムにバグがあるのはすぐにわかることと思います。
<バグの内容>
func.cでグローバル変数ext_cdを宣言して参照していますが、main関数上には変数ext_cdの実体が宣言されていません。
サンプルコードのプログラムをHP-UXで実行すると、シンボル未解決がある処理を実行したタイミングで実体がないことに気づいて異常終了します。裏を返すとバグがある処理を実行するまではシンボル未解決には気づけないことになります。
エラー処理等で普段実行することがない処理にこの手のバグがあると、HP-UXでは潜在化してしまい、マイグレーションのタイミングでバグが顕在化する事態となります。
HP-UX
コンパイル:成功,実行時:未解決シンボルのある処理を実行すると異常終了Linux
コンパイル:エラー,実行モジュールが作れないため、実行不可
「プログラム開発時に全ての命令を実行するテストを実施するので、こんな事態には陥りません」と主張される方もきっといらっしゃると思いますが、残念ながらシステムは生き物です。
リリース後は長期にわたって多くの人が関わって処理の追加/削除が行われれます。その中でテスト不十分な処理が一つでもあると、この問題を作りこんでしまう可能性があります。
【対策】グローバル変数の宣言、参照関係を見直す
この問題を解決するためには、グローバル変数の宣言と参照関係を整理して見直す必要があります。
具体的にはグローバル変数を使用する場合、実体と参照元の関係がわかるように整理すれば大丈夫です。 整理した結果を基に変数の実体宣言を追加したり、グローバル変数の使用そのものをやめるなどの対応を行うことになります。
グローバル変数は、関数での引数渡しが不要となることから、構造体のポインタを渡したり、改修工数を抑えるために影響範囲を局所化するなどの目的で多用しがちです。
しかし、変数のスコープ(有効範囲)があいまいになる等の副作用がありますし、今回ご紹介したようにマイグレーションで手こずるきっかけにもなりますので、適用する際は慎重にしたいところです。
【派生】動的ライブラリ(共有ライブラリ)の設計をおざなりにしていると、この問題にはまりやすくなる!
処理設計の常識として、複数の実行モジュールで同じ処理を行う場合は、その処理を共通関数化することで開発やテストの工数を抑えられ、保守も行いやすくなるという考え方があります。
C言語では、共通関数を「ライブラリ」という単位のファイルにまとめることができます。
静的ライブラリ(アーカイブ)
ファイルの拡張子はa。リンケージエディタ(ld)で実行モジュールを
作成する時、実行モジュールの中にまるごと取り込まれる動的ライブラリ(共有ライブラリ)
拡張子はHP-UXではslまたはso、Linuxではso。リンケージエディタ(ld)
で実行モジュールを作成する時、実行モジュールの中には取り込まず、参照関係があることのみを登録する(=リンクする)
共通関数に修正を入れる場合、ライブラリの種類により作業が異なります。
静的ライブラリ(アーカイブ)
共通関数のコンパイル:必要,実行モジュールのリコンパイル:必要動的ライブラリ(共有ライブラリ)
共通関数のコンパイル:必要,実行モジュールのリコンパイル:不要
修正時の手順が少ないこと、及び実行モジュールのファイルサイズを小さく抑えられるという利点から、共通関数を動的ライブラリ(共有ライブラリ)にまとめているシステムは多いと想定されます。
しかし、ここで動的ライブラリの設計をおざなりに設計していると今回の現象にはまりやすくなります。
たとえばどんな設計がはまるかというと…
共通関数AとBを1つの動的ライブラリ(共有ライブラリ)にまとめている
共通関数Aは、実行モジュールP、Qから呼び出す
共通関数Bは、実行モジュールQ、Rから呼び出す
共通関数Bは、グローバル変数Zを使用しており、変数Zの実体は実行モジュールQ、Rで宣言している
実行モジュールQとRをLinuxでコンパイルする際、動的ライブラリ(共有ライブラリ)にあるグローバル変数Zの実体は実行モジュールQとRに存在するため、コンパイルは成功します。
しかし実行モジュールPをLinuxでコンパイルすると、呼び出し関係のない共通関数Bで使用するグローバル変数Zの実体がないので、コンパイルに失敗するという事態が起きてしまいます。
このエラーを解消したい場合は、ちょっと面倒な対応が必要です。
①実行モジュールPに(使う予定のない)グローバル変数Zの実体を宣言する
②共通関数Aと共通関数Bをそれぞれ別の動的ライブラリ(共有ライブラリ)に分けて、実行モジュール毎にリンクする動的ライブラリを分ける
①は後から見返すと意味がわかりづらいコードが増えることによる保守性の低下、②はmakefileの改修が大がかりになるなど、いいことがありません。
「共通関数は全部ひとまとめにしておけばいい」という安易な考えでいると、上記のように後々のマイグレーションで手こずる結果に陥りますので、動的ライブラリ(共有ライブラリ)の設計はしっかり行いたいものです。
最後に
マイグレーションTipsとして「Cのプログラム移植/コンパイルエラーの原因と対処方法(HP-UX→Linux)」をご紹介しましたが、いかがだったでしょうか。
Tipsは他にもありますので、随時ご紹介してまいります。
HP-UXは最新バージョン11iV3の販売が2023年12月末終了、通常サポートも2025年12月末終了と公表されたため、Linuxなど他プラットフォームへのマイグレーション案件が増えてきています。 マイグレーションに携わる技術者の皆さんに、この記事が目に留まり少しでも役立てて頂ければ幸いです。
マイグレーション支援サービスのご紹介
株式会社SHIFTでは、コンピュータシステムのマイグレーションを円滑に進めるための支援サービスを提供しています。
マイグレーションに関する支援サービスの詳細は以下のとおりです。
システムのマイグレーションをご検討、または課題解決に困っている方がいらっしゃったら、以下のページよりお気軽に問い合わせいただければ幸いです。
お問合せはお気軽に
https://service.shiftinc.jp/contact/
SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/
SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/
SHIFTの導入事例
https://service.shiftinc.jp/case/
お役立ち資料はこちら
https://service.shiftinc.jp/resources/
SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/