新・日々録 by TRASH BOX@Eel このページをアンテナに追加 RSSフィード

2018-12-01

「C言語書けます」のボーダーラインはどこか?

C言語を覚えると組込み系の採用に有利」みたいな就職・転職向けの文言を見る度に、世の中そんなに甘くないと言いたくなるのです。

多分、組込み開発に限らず、例えばLinuxコンソールアプリ等でC言語を散々使った人も同意するのではないかと思うのだけど、Cプログラマは他のCプログラマに厳しいものだ。

そもそもC言語自体が鋭い刃物に例えられる代物で、メスにもドスにもなりうる取り扱い注意な言語だ*1。だからCプログラマは誰しも、C言語について一言居士となる。

そんな連中からすれば、C言語の基本文法を知っていて簡単な課題を解ける程度の人は、「C言語を書ける人」には含まれない。

では、どのレベルの人が「C言語を書ける人」に該当するのか?

よくよく考えてみると、次の点をクリアしているか否かがポイントではないかと思う。

  1. C言語の各種文法の「効率的で理にかなった使い方」をある程度知っている。
  2. C言語とメモリモデル(スレッドじゃなくてハードウェアアーキテクチャの方)にまつわるアレコレをある程度把握している。

(1) は、例えば普通のCプログラマ関数引数として構造体の実体ではなくポインタを指定するように実装するものだが、そういう癖を身に着けていているか、そうする理由を知っているか否か――ということだ。

この辺は、師匠となる本や人に揉まれながら、C言語でそこそこの大きさの実用的な何かを実装した経験によって成長する部分だろう。

ぶっちゃけた話、(1) はC言語に限らない話で、他のどの言語でも同じだ。C言語が特異なのは (2) の存在だ。

(2) は、要するに『Cプログラミング専門課程』で語られているような、C言語とメモリに関する諸々をどこまで体得しているか、という話だ。

プログラミング言語ハードウェアに対するある種の抽象化層として機能するのだけど、C言語はその抽象化層が薄くて且つ容易に穴をあけることができる*2。で、穴の中にはメモリ空間が広がっている*3

穴は簡単にあいて容易に拡がる代物であるし、Cプログラマもまたよせばいいのに好き好んで穴をあけようとする連中なもので、どうしても穴の中のメモリとの付き合い方を覚える必要がある。うまく付き合えないCプログラマは翌朝顔にひっかき傷をこさえて出勤する定めにある。

そもそも組込み以外のソフトウェアであっても、ファイルやTCP/IP経由で外部とバイナリデータ*4でやり取りすることになった時点で「メモリ上のデータの形式」というメモリとのお付き合いが始まるものだ。組込みでなくともC言語を使うなら、メモリとの上手な付き合い方は必須の教養だ。

抽象化層の薄さと穴のあけ易さはC言語(とC++)の特異な部分だ。他の言語はもう少し厚い抽象化層を持っているし、穴があきにくいように工夫されている。だから大抵の言語では抽象化層(言語機能とその有効な使い方)を学べば大半の物事が済むのだが、C言語では言語機能という抽象化層を学ぶだけでは不十分で、穴の中のメモリを知る必要がある。

で、メモリとの付き合い方を毎年指導するのがしんどいので他人任せにしたいのだが、唯一使える本だと思っている『Cプログラミング専門課程』は入手困難なのである。

Cプログラミング専門課程

Cプログラミング専門課程

代わりの本、何か無いかなあ?

*1:この例えの元ネタは藤原博文氏の『Cプログラミング診断室』である。

*2:だからこそドライバのような「ハードウェアを直接叩く」系のソフトを書くことができるのだが。

*3:組込みソフトウェアの場合は物理メモリだし、WindowsLinuxなどのアプリの場合は論理メモリだ。

*4:ここでは「テキスト形式ではないデータ」ぐらいの意味で使っている。

2018-11-09

「組込み開発=C言語」というイメージはどこまで正しいか?

組込み開発でのプログラミング言語というとC言語(時にアセンブラ)が真っ先に挙げられる気がするのだが、実務的にどこまで正しいのか書いておこうと思う。

結論から言うと、組込みプログラマの立場としては「組込みプログラミングC言語」は概ね正しいのだが、組込み業界という視点では「C言語以外も結構使われている」ということになる。

そもそも組込み業界ではどんなソフトウェアが作られているだろうか?

組込み業界と言っても結構広いので、分野によって色々と異なるものだが、私が関わっているのは「ガジェットスマート家電」寄りの分野だ。作っているソフトウェアは、組込みシステム向け三層アーキテクチャドライバミドルウェアアプリケーション)が適用される程度には大きい*1OSRTOSの類が大半だ。

で、外注も含めて、書かれているソフトウェアは以下のような感じだ。

  1. ハードウェア制御用のドライバファームウェア
  2. 移植性のあるミドルウェア
  3. 組込み機器のアプリケーション
  4. 組込み機器をパソコンと接続するためのWindowsデバイスドライバ
  5. 組込み機器と連携するWindowsアプリケーション
  6. 組込み機器と連携するmacOSアプリケーション
  7. 組込み機器と連携するAndroidアプリケーション
  8. 組込み機器と連携するiOSアプリケーション
  9. 組込み機器と連携するWebサービスクライアントサイド
  10. 組込み機器と連携するWebサービスサーバサイド

今の時代、リッチな組込み機器はUSBネットワーク経由で他のサービスと連携する仕組みを備えている。で、連携先の数だけ必要なソフトウェアが増えている。

接続方式が一般的なプロトコルで十分であるとか、外部デバイス側のフレームワークで提供されている通信機能で十分な性能が得られるような場合は、外部デバイスとのやり取りの部分を仕様で定義した上で、外部デバイス側のソフトウェアの開発を外注することが多い。

例えばWebサービスサーバサイドが一般的なRESTアーキテクチャのWebアプリケーションで十分ならば、Webアプリケーション側のWeb APIURL通信データの形式を決めた上で、Webアプリ専業の会社に発注してしまうのだ。

しかしちょっと特殊なことを実現しようとなると、サーバサイド側の実装も自分たちでコントロールしたくなるので、サーバサイドの初期実装まで自分たちで行うことになる。その後はシステムの性格次第で、運用だけ外注してしまうか、運用と改修を共に外注してしまうことになる。

同じことはモバイルアプリにも言える。機器との接続方法がSDKに用意されている一般的な機能で済むとか、アプリUIが凝ったものでなくて普通のUI部品で済むようなケースでは、外注することが多い。しかし特殊なことを実現したいとか、UIを凝りに凝ったものにしたいとか、そういう事情がある場合は内製することが多い。

先の一覧でいうなら、(1)〜(3) はほぼ確実に自分たちで開発するが、(4) 以降は開発するシステムの性格次第だ。アプリのデザインや性能要求が絡んでくる場合は、自前のPCアプリモバイルアプリ開発部隊を持っていたりする。

実際に組込み機器内で動作するコンポーネントである (1)〜(3) のうち、ステレオタイプの組込みプログラミングに最も違いのは (1) だ。ドライバファームウェアを担当している人は名実ともに組込みプログラマである。彼らの主要言語はC言語だ。その意味で、組込みプログラマの感覚では「組込みプログラミングC言語」となる。

一方で (2) のミドルウェア屋は「ハードウェアから分離させた移植性のあるモジュール」を開発するのが主要業務なので、C言語使いではあるものの、組込みプログラマという意識はやや薄い。(3) の組込み機器のアプリケーション層担当ともなると、ミドルウェアドライバAPIを叩くのでハードウェアを直接制御することは無いし、LCD表示用のGUIツールキットとの絡みでC++を使うことも多々ある。

なので、組込み業界の中のどの分野かに依存する話ではあるが、組込み系の企業に就職した人が使うプログラミング言語C言語であるとは限らないし、誰もがハードウェア制御用のコードを書ける訳でもない、という現状がある。

サービス展開も含めて規模の大きなシステムの場合、そのシステムを開発している企業の中で分業化が進んでいるものだ。だから、若手の頃から組込み機器のアプリケーション層の開発をバリバリやってきて、C++ベースのオブジェクト指向プログラミングの手練れになった反面、ハードウェアを直接叩くドライバの開発経験がない――みたいな人が実在したりする。

私自身、キャリアの大半が三層アーキテクチャミドルウェア層の開発なので、組込みらしいハードウェア制御プログラムを書いたのは2度だけだし、その開発も業界に入ってから10年以上経ってから偶然遭遇したものだった。

ということで、分野を選ぶ必要はあるものの、C言語が使えなくても組込み業界に入ることは可能だ。結構、機器と連携するモバイルアプリ開発とかで、AndroidiOS向けアプリ開発の手練れを探している企業は多いと思う。

ただし、少し特殊な要件がある場合は、例えばモバイルアプリ開発でもC言語C++知識が求められることがある。リアルタイム性に関する性能要件がやや厳しいので核となる部分をCやC++で実装する必要があるとか、機器とのTCP通信にて独自のプロトコルを使う必要があってクライアント側としてCやC++で実装されたモジュールが提供されるので組み込む必要があるとか、そんな感じ。なのでAndroidならNDKの、iOSならC++11のスマートポインタObjective-C++の知見*2要求されることになる。

(だから先の一覧のmacOSiOSAndroidアプリの開発言語にC++を含めている)

結論として、繰り返しになるが、組込みプログラマ的には「組込みプログラミングC言語」という構図は概ね正しい。しかし組込み業界という俯瞰した視点では「C言語以外も結構使われている」ということになる。業界にはデバイス制御プログラム以外のソフトウェアを書いている人もそれなりにいるのだ。

なお、上記議論は「製品としてリリースするソフトウェア」を前提としている。開発用の小ツール――内製ツールプログラマ個人ツールとしては、テキスト処理に長けたスクリプト言語Excel制御用の言語(VBAPowerShell)がごく普通に採用されているものだ。ただ、全般的にC言語C++(better C)畑の人が多いので、Windows向けデスクトップアプリ開発では(CやC++モジュールをそのまま組み込むことができる)MFCQt選択されやすい傾向にはあると思う。

*1:規模の小さなマイコンプログラミングだと、せいぜい「ドライバアプリケーション」の二層のアーキテクチャであるし、モノによっては明確な分離がされていないごった煮の実装となっている場合もある。

*2:CやC++モジュールObjective-C++でラッピングして、インタフェースObjective-Cとして見せることで、Swiftから楽に使えるようにするのである。その際に、Objective-CのARCと歩調を合わせてリソース解放するように、C++11のスマートポインタを使う。

2018-10-06

ジュニアは採用できてもノービスは採用できない。ベテランになれないジュニアは淘汰される。

ジュニアを採用しない連中はシニアに値しない - portal shit!」を受けて。

そもそも「ジュニア」がどの程度の人を指し示すか、という話はあるが、アメリカの話っぽいので、おそらく最低でもCS(コンピュータ・サイエンス)の学士は持っているだろうし、修士や博士を持っている人もそれなりにいるだろう。

で、「履歴書の順序づけ - The Joel on Software Translation Project」からエスパーすると、アメリカのテック企業はCSの学位そのものの有無に拘っているのではなくて、「学位を持っている=CSの知識を持っている」という判断で履歴書をふるい分けた上で、次の採用ステップ(電話面接など)に進めているのだと思う。日本企業でも、例えば電子機器メーカーの技術者採用でその手の学部卒を採用しているものだが、それと似たようなものだろうか? ただアメリカのテック企業や日本の中小ベンチャー企業の場合、ソフトウェア開発の経験者(それこそ創業者)が履歴書のふるい分けに関わっているなら、CSの学位が無くても特筆すべき事項(例えば有名なOSSのメンテナである等)が書かれていたら履歴書のふるい分けで落とされることは無い――という印象がある(実際のところは不明だが)。

ついでに、「どうしてプログラマに・・・プログラムが書けないのか?原文)」を読んでアメリカのテック企業でのソフトウェア開発者採用事情を推測するに、流石にプログラムを書けない人はアウトだと思われる。

つまり「ジュニア=CSの知識を持っていて、簡単なコードぐらいは書ける人」である。

「ジュニアを採用しない連中はシニアに値しない」というのは、裏返せばソフトウェア開発者を採用する際の足切り基準がジュニアであると言っているに等しい。

そして、空気を読んで書かれていない行間を推測するなら、ジュニアに満たないノービス*1は考慮に値しない、ということである。CSの学位を持っていないとか、学位を持っていてもFizzBuzzのような簡単なコードすら書けない連中は、そもそもスタートラインに着くことを拒否される可能性が高い(ただし先に挙げた「例えば有名なOSSのメンテナである等」の天才枠は別で、CSの学位が無くても拒否されないだろう)。

あとアメリカの企業の場合、日本の企業よりも「職務不適格による解雇」は行われやすいだろう。

だから、スタートラインに着くことができたジュニアも、ベテランやシニアにステップアップできなければ、少なくとも職業プログラマとしては淘汰されるだろう(もちろん、キャリア開発を経て別分野に転身するなら話は別だが)。

一方で、元記事にもあるように、企業側はジュニアを受け入れてキャリア開発を支援する体制を整える方が健全だろう。誰だって最初は(職業プログラマとしては)素人なのだ。

ところで、ジャパニーズ・トラディショナルな新卒一括採用では、企業によっては「計算機科学とは無縁で、プログラムを書いたこともない人」みたいなノービスがソフトウェア開発者として採用されることがある。採用時に所属学部などでフィルタしている場合も、採用の過程でプログラムを書かせるところは稀だろう。

つまり日本企業では、ソフトウェア開発者としてジュニアに満たないノービスが採用される可能性が(少なくともアメリカのテック企業よりも)高い傾向にある。

あと、「職務不適格による解雇」に値する水準が高く設定されている(でないと解雇の妥当性をめぐる訴訟リスクを抱える)点もアメリカとは異なる。

そういった日本独自の事情を勘案した上で、幾分か割り引いて件の記事を読むべきだろう。

個人的には、ジュニアを採用した場合でも彼/彼女がベテランに育つか否か博打な面があるのに、ジュニアよりも教育コストがかかる――そして心が折れて数年で退職してしまい、それまでかけたコストが水泡に帰する可能性がある――ノービスを採用するのはリスクが大きいと思う。

せめて情報系の学部卒や専門卒に絞り込むとか、もしくは「職業訓練で半年座学と実習を受けながら基本情報技術者を取りました」ぐらいに気合いの入った退路の無い人でないと……教育するにしても、ジュニアとノービスではスタート位置が大きく異なるからなあ。

*1:フィギュアスケートの競技会の年齢別クラスはシニア、ジュニア、ノービスらしいので、ジュニアよりの下のクラスを意味する言葉としてノービスを使っている。

2018-09-22

umask(1)は引き算しない、多分。

umask(1)の仕様を調べようとしたのである。

で、ググって見つかった「デフォルトのファイルアクセス権 (umask) (Solaris のシステム管理 (基本編)) 」を見てちょっと驚いた。

設定する umask の値は、与えたいアクセス権の値を 666 (ファイルの場合) または 777 (ディレクトリの場合) から引きます。引いた残りが umask に使用する値です。たとえば、ファイルのデフォルトモードを 644 (rw-r--r--) に変更したいとします。このとき 666 と 644 の差 022 が umask コマンドの引数として使用する値です。

デフォルトのファイルアクセス権 (umask) (Solaris のシステム管理 (基本編))

「引きます」とか「引いた残りが」とか、引き算をしているかのような記述である。

職業病だと思うのだが、私は `mask' という単語より、ビット演算によるマスクを行っているものと予想していた。しかしこの文書のニュアンスでは、ビットマスクではなく引き算を行っているように読み取れる。

日本語に翻訳する際に微妙な訳になったのかと思ったが、Oracleの別の文書である「umask(1) (man pages section 1: User Commands) 」の記述はこんな感じだった。

The value of each specified digit is subtracted from the corresponding ``digit'' specified by the system for the creation of a file (see creat(2)). For example, umask 022 removes write permission for group and other (files normally created with mode 777 become mode 755; files created with mode 666 become mode 644).

umask(1) (man pages section 1: User Commands)

`subtracted' という単語より、私の拙い英語読解力では「引き算している」という感じに読み取れる。

しかし「no title」には別の記述がされていた。

新しく作成されたファイルに設定されるパーミッションビットの値は論理非含意 (付加) を使って計算され、論理記号で表現することができます:

R: (D & (~M))

つまり、最終的なパーミッション R はデフォルトのパーミッション D の論理積とファイル作成時のモードマスク M の論理否定が合わさった結果になります。

no title

ArchLinuxのドキュメントでは、引き算ではなくビットマスクしているものと読み取れる。

どちらが正しいのだろうか? 

POSIXのumask(1)の説明には以下のように「logical complement」とあるので、ビットマスクしているっぽいように思うのだが……。

For a symbolic_mode value, the new value of the file mode creation mask shall be the logical complement of the file permission bits portion of the file mode specified by the symbolic_mode string.

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/umask.html

試しに、挙動を検証する方法を考えてみた。

まず、引き算とビットマスクとで計算結果が異なるケースは存在するだろうか? マスクが0022の場合、引き算とビットマスクとで計算結果は同じだ。しかしマスクが0033の場合、ファイルの場合の既定値(0666)との組み合わせにおいて、計算結果が異なるはずだ。

以下のプログラムC言語)で確認したところ:

#include <stdio.h>
#include <stdlib.h>

static void pmask(const int bmsk, const int umsk)
{
    (void) printf("%04o -  %04o == %04o\n", bmsk, umsk, bmsk -  umsk);
    (void) printf("%04o & ~%04o == %04o\n", bmsk, umsk, bmsk & ~umsk);
}

int main(void)
{
    pmask(0777, 0022);
    pmask(0666, 0022);

    pmask(0777, 0033);
    pmask(0666, 0033);

    return EXIT_SUCCESS;
}

実行結果はこんな感じだった。

$ ./a.out
0777 -  0022 == 0755
0777 & ~0022 == 0755
0666 -  0022 == 0644
0666 & ~0022 == 0644
0777 -  0033 == 0744
0777 & ~0033 == 0744
0666 -  0033 == 0633
0666 & ~0033 == 0644
$ _

0666と0033の組み合わせのみ、引き算では0633が、ビットマスクでは0644が得られる。しかし他のパターンでは引き算とビットマスクとで得られる値は同じだ。

手元のUbuntu 16.04で試してみたところ、「umask 0033」の時に生成されたファイルのパーミッションは「rw-r--r--」、すなわち0644だった、同じLinuxであるArchLinuxと同様に、umask(1)では引き算ではなくビットマスクで計算されているようだ。

ということで、少なくともUbuntuとArchLinuxでは、umask(1)の結果は引き算ではなくビットマスクだ。

問題は、事の発端のドキュメントのOSであるSolarisを含む「Linux以外のUnix系OS」でのumask(1)の挙動を調べていないことと、元文書の「subtract」という単語が引き算以外のニュアンスを持っているか否かが分からないこと、以上の2点だ。実際、どうなのだろう?

2018-08-28

Raspberry Pi用にQtをクロスコンパイルする――ただしQtはX11上で動かしたい

Raspberry Pi用にQtをクロスコンパイルする方法を探していたのだが、どの情報も非X11のコンソール環境にてEGLFS経由で直接OpenGL ESで画面描画する構成のQtバイナリを作るものばかりだった。

しかし私は、諸事情よりX11のデスクトップ上でアプリを実行できる構成のQtバイナリが欲しいのである。

まあ、Linux上で「Qt for Linux/X11」を自前ビルドするのと同じ方法をRaspbian上で実行すれば、確かにブツは手に入る。だけどPC上でビルドする方が速いので、できればクロスコンパイルしたい。

Raspberry Pi向けのQtクロスコンパイル(EGLFSを使う版)

この文書を書いている時点で、最も有用な文書はQt WikiRaspberryPi2EGLFSだ。このページの方法でEGLFSを使う構成でQtをクロスコンパイルできる。

以下、上記ドキュメントの補足:

  • 上記ドキュメントでは、Raspberry Piの開発元でメンテしているクロスコンパイラを使用している。Raspberry Piの公式ドキュメントKernel buildingによると、中の人はUbuntuでこのクロスコンパイラを使うことが多いらしいので、実績のあるUbuntu上でクロスコンパイルした方が無難かもしれない。
  • 事前にRaspbian側でssh接続を有効化しておく。
  • rsync(1)でファイル転送する際は、上記ドキュメントのrsync(1)の実行例のオプションに「-e ssh」を追加して、ssh経由で転送する。
  • Qt 5.11系をクロスコンパイルする際は、「./configure」のオプションに「-no-gbm」を付与する。でないと「make」した時にビルドに失敗する。
  • /etc/ld.so.conf.d/」に配置するファイルの名前は、「qt5pi.conf」ではなく「00-qt5pi.conf」にする必要がある。

X11用にクロスコンパイルするには?

色々調べた結果、先に挙げたドキュメントの「./configure」のオプションを以下のように調整することで、RaspbianのX11上でアプリを実行する構成でクロスコンパイルできることが分かった。

  1. オプション「-opengl es2」を「-opengl desktop」に変更する。
  2. オプション「-no-eglfs -qpa xcb」を追加する。
  3. Qt 5.11系をクロスコンパイルする場合はオプション「-no-gbm」を追加する。

他の作業については、特に変更点はない模様。