cannot refer to declaration with an array type inside block エラー

C のブロック内(^ に続く関数のあれ)では、配列への参照はできないらしい。

わかりやすい例がここにあった。

void block_arr()
 {
   int res = 0;
   int i[4] = { 3, 4, 4, 1 };
   int* j = i;
  
   int (^test_block )(int) = ^(int num)
     {
       return num + i[1];      // This is an error: "error: cannot refer to declaration with an array type inside block"
 //      return num + j[1];    // This would work
     };
  
     res = test_block(7);
 }

なんでこんな制限があるのか最初よくわからなかったのだが、そこの人いわく、「ブロック内で配列を使ってしまうと配列全体をコピーする必要があるため、非効率だ。ポインタは OK 。」ということらしい。

 

strlen と sizeof にまつわる不具合

以前に Mac 環境で C を使う場合、バイナリバイト列などのデータは NSData に任せた方がいいということは述べた。

いうまでもなく、C のデータの取り扱いの煩雑さは負担と考えているからだ。

そうはいっても、 既に(Objective ではない素の)C で各種データの定義が完了しているような場合、そういうわけにもいかない。例えば C で書かれた汎用ライブラリの機能に大きく依存しているようなプロジェクトではどうしてもどうなってしまう。

ここら辺、どの部分を NSData (というか cocoa の各種フレームワーク)に割り振り、どの部分を C で押し通すのかは、センスの出るところだと思う。

ところで、C 言語に関しては、巷の C 談義のようなものだけではうまくいかないと感じている。

ポインタの話は C を語る上で重要だが、ポインタだけ知っていても実用的なコードが書けるとは思えない。

ところで、この前、さるプログラムに不具合が出て、原因を調べていて気がついたのだが、以下のコードを実行した場合、結果はどうなると思います?

uint8_t AP1[] = {
        0x00, 0x20, 0x00, 0x88, 0x04
    };
printf("strlen(AP1): %d\n", strlen(AP1));
printf("sizeof(AP1): %d\n", sizeof(AP1));

この部分だけを取り出すとわかりやすいと思うが、結果は

strlen(AP1): 0
sizeof(AP1): 5

となる。

「終端文字 0x00 が先頭にあるのだから、文字列の長さを求める strlen では 0。使われているメモリのバイト数を求める sizeof では 5。当たり前じゃないか」と言われるかもしれないが、実際の(不具合のあった)コードはこんなにわかりやすくはない。

具体的には AP1 の末尾にバイト列を追加してバイト列を生成するためのメモリ確保の段階で

malloc(strlen(AP1) + strlen(hoge))

とやっていたんだな。

実は、これらコードを含むプログラムは USB を制御するコマンドで AP1[5] もそのコマンドの一つだった。

実際にデバイスを制御するわけだから、コマンドといえどもバイナリデータであってもおかしくはないのだが、そこら辺の事情をわからず「コマンド」と言われれば文字列を連想するのが普通ではなかろうか。

で、文字列と思いこんでこのような実装になってしまったと。

この手の不具合が発見しにくいのは、0 を含まない「コマンド」の場合は正しく動作してしまうからだ。

なお、バイト列とバイト列を結合させたい場合には

   uint8_t cmd[sizeof(AP1)+sizeof(hoge)];
    memcpy(cmd, AP1, sizeof(AP1));
    memcpy(cmd+sizeof(AP1), hoge, sizeof(hoge));

出力させる時は

for (int i = 0; i < sizeof(cmd); i++) {
            printf("%02x", cmd[i]);
    }

と書くとわかりやすいでしょうか。

 

arm Mac で C/C++ ライブラリを x86_64 向けにビルドする

すっかり arm 環境に移行した感のある Mac 界隈だが、なんらかの事情で Intel Mac 向けにアプリなどをビルドしたいという場合がある(案件だとね)。

アプリ自体は Xcode 上での操作でなんとかなると思うのだが、厄介なのはライブラリの類。

使いたい C/C++ のライブラリを M1/M2 Mac 上で x86_64 向けにビルドする必要が出てくる。
いわゆるクロスコンパイルというやつだ。

iOS の場合、simulator が x86_64 でしか動かないので、この手の情報はけっこうあったのだが、 MacOS 向けの情報は皆無だった。

試行錯誤したところ configure 時に以下のようなにコンパイルオプションを与えると、クロスコンパイルできるようだ。

./configure --prefix=/dir/hoge x86_64-apple-darwin64 CFLAGS='-arch x86_64 -O2 -g'

あとは make, make install でいけるっぽい。

–build オプションを使えとか、それっぽい情報はあるにはあった。
が、実行しても惜しいところまではいくんだが、なぜか最終的にはうまくいかなかった。

 

Xcode -aggeragate target-

ところで、であるが、アイキャッチの赤い三重丸のようなやつの正式名称を知っているだろうか?

aggeregate target

どことなく射的の的を連想させるアイコン(ターゲット)は aggeregate というらしい。

英単語としての発音は「アーグリゲイト」、一般的な意味は「集約する、合計する」だ。

日本語の情報はほとんどなく

ライブラリ・SDKにSwiftLintを導入するベストプラクティス

でかろうじて触れられているのみ。

aggregate target を生成するのもややクセがあって、

File -> New -> Target…

として(↓)、

ダイアログを表示させ iOS や macOS ではなく other の方を選ぶ。

これは調べないと分かりませんね。

参考

その他、Xcode やビルドシステムについて。

・cmake 単体で iOS のプロジェクトを取り扱う。

cmakeを使ってxcodeを使わずにiosアプリを作成する

・cmake で Xcode のプロジェクトファイルを生成する。

CMakeとIDEを連携させる

help によれば cmake で framework を直接生成できるらしいのだが、試したことはない。
(→結局、試しました

余談 -External Build System-

ところで、アーグリゲイトなターゲットを作成する際、こういう画面が出てくる。

気になるのは External Build Sysytem だと思うが、イマイチ使い方がわからない。

デフォルトだと make を使うように設定されているのだが、aggregate ターゲットでもいけるはずだが???

余裕があったら、調べます。

余談 Xcode での C++ 標準ライブラリのヘッダーファイルの置き場所

Xcode14 では

/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1

です。

よく移動になるみたいですね。

標準ライブラリのヘッダーファイルが見つからない場合は、上のフォルダを header search paths に設定する。