C/C++ 覚え書き -ポインタ-

やはり「C/C++ は難しい」という声をよく聞く。

実際、最近、twitter でこんなやりとりがあった。

ワイも極めるレベルまではいってないが、まあ困らない程度には使える。
古今東西の C で書かれたライブラリを使うために悪戦苦闘しているうちに何となく覚えてしまった。

C/C++ の習得を難しくしている原因の一つは、ポインタだろう。

ポインタの基本

twitter でも書いたが、ポインタの簡単なサンプルを上げておくとこんな感じか。

//test.c
#include <stdio.h>

int main(){

    //int *a;
    int* a;

    int b=100;

    a = &b;

    printf("%d", *a);

}

これをコンパイルする。素直にコンパイルするなら、こんな感じか。

gcc -o test test.c

これで test という実行可能なファイルができる。
実行させると、もちろん

100

と表示される。

わざわざ

int *a

も書いておいたのは、こう書いても結果は変わらないから。
ただ、こう書くと初学者の理解はがこんと悪くなるという印象を持っている。

なんで、ここでわからなくなるかといえば、おそらく、宣言時の *a と最後の printf で( a で差し示されている)実際の数値を表示させる *a を混同してしまうからだと思う。

ポインタ変数といえども変数には変わりなく、宣言が必要だ。もちろん、型も必要。

上の場合は、int 型のポインタ変数 a を宣言したいのだから、int* と書いた方が(少なくとも初学者にとっては)混乱は減ると思う。
あくまで変数としては a なのだから。

通常の変数と違うのは、ここにアドレスが格納されるという特殊性だろう。

a 自体はアドレスなのだから、その(アドレスで示されている)実体を知りたければ何らかの操作が必要で、シンプルに * をつける。
* は宣言時にも使われるので、両者を混同してしまうのだろう。

次の難所 -文字列と配列-

ポインタの概念が分かってもそれだけでは使えないと思う。

これはおそらく C の配列や文字列の扱いのせいでしょう。
取り扱いというか約束事ね。

配列

上のようなプリミティブな数値型の場合、ポインタを使いたければ * を使えばよかったが、配列も同じようにするとうまくいかない。

つまり

int[] *a;

という表現はしない
というかする必要がない。

というのは、C では、配列 array[] などと書いた場合、array つまり配列変数名自体はその先頭要素へのポインタになっているからだ。

サンプルコードを示すと以下のようになる。

#include <stdio.h>

int main()
{
    //int[] *p;
    
    int array[]={1,2};
    
    printf("%d",*array);
    
    return(0);

}

結果は 1 。
int[] *p という宣言は、エラーになる。

ところで、こういう書き方は、近年の配列に要素数が簡単に追加できたり型推論してくれたりする言語に慣れた人からすると、奇異に映るようだ。
奇異というか不安に近い感情だろうか。

array に要素を追加したい場合、どうするんですか?と。

安心して欲しい。C では、原則、要素数の追加はできない
(C での可変長配列に関してはここらあたりの記事参照)

なお、この場合、配列 array の要素数を知りたければ、以下のようななんとも面倒くさい書き方になる。
こちらのオンライン C コンパイラーサイトを使用)

array 自体のメモリサイズを sizeof で求めて、それを要素のバイト数で割って、ようやく要素数を知ることができる。

このコードにしても unsigned 絡みでワーニング出てますけどね。

ついでで言っておくと C/C++ ではファイルサイズを調べるときも(直接これを調べるような関数のようなものは提供されていないので)同様に若干手間のかかるアプローチを取る必要がある。

このツィなど参照。

大きさを調べるときには注意が必要

なお、(初学者は絶対混乱すると思うが)array[] という書き方と *array という書き方は C コード内で厳密にはまったく同じ挙動をするわけではない。

よく知られた例としては、sizeof(*array) とするとポインタの大きさを返すのみで、array 自体の大きさを返しているわけではない

バグの原因となる。

ちょっと実用的なサンプル -バイナリファイルを 2byte 毎に読み込む-

ここら辺の仕様はややこしいところだが、実務的には便利なときがある。

あるバイナリファイルがあった時、これをバイト配列ではなく、2byte や 4byte の数値配列として読み込みたいということがよくある。
C++ になってしまうが、以下のようなコードはよくお目にかかる。

    const char* fname="(PathTo)/sample.raw";
    std::ifstream ifs(fname, std::ios::binary);
    ifs.seekg(0,std::ios::end);
    uint64_t size = ifs.tellg();
    ifs.seekg(0);

    uint16_t *data16 = new uint16_t[size/2];
    ifs.read((char*)data16,size);

このコードのキモは

ifs.read((char*)data16,size)

だ。
ifstream::read() は、仕様的にはバイト配列しか読み込めないが、2byte 配列 data16 のポインタをバイト配列のポインタでキャストすることで 2byte の配列として読み込んでくれる。

いったん、バイト配列として読み込んだ後、2byte の配列にマッピングし直して・・・という複雑な処理は必要ない

これをやるためには、ファイル自体の大きさ(バイト数)を予め知っておく必要があるが、その処理は前段で行なっている。ファイルの大きさ一つとってもこういう書き方をする。

なお、このコードの意味を理解したときはいたく感動した。

ポインタが使われている例としてよく知られたものだが、もう一つ例示しておくと main(argc, *argv[]) の意味もわかってみるとなんということはない。

こちらの記事あたり参照。

配列を舐めてはいけない

ところで、ポインタの話をすると、そこだけに注力してしまい、その他の知識が疎かになりやすい。

ポインタと配列の関連が深いのは上でも述べたが、実用的なプログラミングでは両方の知識を絡ませないとにっちもさっちもいかない。

C の配列で知っておかなければいけない知識は、

配列は初期化しないと使えない

という(ポインタに比べるとあまり注意を払われないんだが)超重要仕様。意外に理解してない人が多い。

初期化には、最初から全てを代入するとか初期化子を使う方法とかいくつかやり方があるんだが、最近のワイの好みは memset などを使って、根こそぎメモリ領域を確保しておく方法。

uint16_t testarray[100];
memset(testarray, 0, sizeof(uint16_t)*100);

あるいは(ややこしいが、こちらの方が実用的)、

uint16_t *testarray = (uint16_t *)calloc(100, sizeof(uint16_t));
// any code you want
free(testarray);

これで全ての要素に 0 が入るので、この初期化以降は testarray[50] だのに好きな値を代入できる。

もちろん、C の配列の使い方(可変長が許されない&初期化する必要がある)は、不便を感じることが多く、例えば MacOS/iOS のアプリを作成する際には、配列のみで攻めていく、ということはないと思う。

なお、最近、実感しているのは、NSData の優秀性。

長さが不定な重要データは NSData 型で管理し、具体的な処理は C ライブラリに任せるとかやると混乱は減る。

「ポインタ」は重要だが、ポインタだけじゃダメって話です。

参考:『配列を自由自在に作る』など。
C で配列が使いにくい→ malloc などで対応、という流れが簡潔に説明されている。
malloc + memset = calloc という感覚なのだが、具体例が出たときにでも追記したい。
こちらの記事もいい。

最初はピンとこないかもしれないが

配列[変数]

となっている配列を扱いたいときにここら辺の知識があやふやだと、うまく扱えない。

逆に「C 使えるようになってきたかな?」と実感できるのはここら辺の知識が身についてきたとき。

Xcode プロジェクトで C/C++ でファイル入出力を扱うとき

アップルの話が出てきたので、ついでに。。。

最近、プライベートでは Mac 使う場合が多いが、これ(↓)知ってないとハマるかも。

デフォでは、ファイルアクセスに制限がある

BAD_ACCESS 関係のエラーが出たら、設定を見直しておきましょう。

参考

C 言語における 16 進表記文字列⇄バイナリ列変換』この配列操作はしばしば話題になるが、これで決定かな。
magicarray を使ったロジックが美しい。

 

C++ 感覚を取り戻す

IT 業界でフリーでやっている若い世代は、仕事を選べる立場にある人はそんなに多くはないと思う。

そんな時に C++ 案件が舞い込んでくると、意外に基礎がすっぽりと抜けてたりする。

そういうわけで急いで C++ 感覚を取り戻すためのメモ。

ワイの場合、Objective-C や Qt も扱っているので、この変更が意外に手間かかったりする。
「似ているようで違う」というのは、思っている以上にタチ悪いかも(笑)。

ハロワ

あまり C++ っぽくはないが、まあ基礎。

//hello.cpp
#include <iostream>

int main(){
    std::cout << "hello world";
}

ソース自体は C とあまり変わらない。

が、コンパイルは

g++ -o hello hello.cpp

または

clang++ -o hello hello.cpp

とする。
gcc や clang では、c と解釈するのかエラーになる。

 

(続く。『江添 C++ 本と本家ストラウストラップ本』あたりもご参考に)

Mac を新調したらやること

Mac を新調したらやること。

Finder がらみ

拡張子は「表示」、隠しファイルも「表示」に変更する。

後者はターミナルから設定する必要があるが、いつも忘れてしまうためメモ。

defaults write com.apple.finder AppleShowAllFiles TRUE

ついでで

killall Finder

とやっておく。

他に apple 製品を使っているようなら、Finder サイドバーは Air Drop を表示させておいた方があとあと便利。

時刻表示

US キーボード使っているとデフォルトではアメリカ時刻になってしまうようだ。

apple メニュー -> 一般 -> 日付と時刻 で以下のパネルを表示させる。

位置情報をオンにしているなら、「現在の位置情報に基づいて、時間帯を自動的に設定」を有効にするのが一番簡単。

GitHub の SSH 接続

GitHubでssh接続する手順~公開鍵・秘密鍵の生成から~

あたり参照。

homebrew がらみ

最近は(個人使用でも)マルチユーザー環境で使うことを意識している。

1 台の Mac でマルチアカウントで homebrew を使う

あたりを参照。

ただし、管理者アカウントのうち一人は、(自分の環境ではなく)従来通り Mac 本来の環境にインストールしておいていいかも。

Java がらみ

ワイは arm Mac では Azule というところの JDK を採用している。

こちらがダウンロードページ。.dmg を落とすのが一番楽。

プロジェクト管理には maven を使っているが、これまではバイナリなどを /usr/local や /opt に直置きしていた。
が、ユーザーによっては maven のバージョンも換えたいということがあるかもしれない、ということで、/(ユーザーホーム)/opt 以下に置くことにした。

ダウンロードページはこちら

NetBeans のダウンロードページはこちら

PostgreSQL がらみ

arm Mac 出始めの頃は、PostgreSQL.app はおろか homebrew も arm Mac にネイティブで対応していなかった。

そのせいか、macports を使って導入するケースが多かったように思う。

が、現在では両者とも arm 対応になっているので、どちらかで入れればいい・・・のだが、ここらへんはポリシーによるかも。
繰り返しになってしまうが、マルチユーザーを意識すると個々のアプリは、ユーザー各自の環境にインストールした方がいい。

だが、データベースサーバーなどを個々の環境毎に変える必要あるのか?という気はする。

こういうポリシーならば macports 由来で入れた方がいいかもしれない。

基本的なアプリなど

chrome サファリはあまり使わないんで。

Visual Studio Code エディタはお好みで。

apple の開発者証明書の引っ越し

apple に developer 登録している場合、開発者証明書を引っ越しておいた方がいいでしょう。

古いMacから新しいMacへ鍵付き証明書を持ってくる

あたりに具体的な手順が書いてある。

 

(続く)

 

 

Python の言語仕様で驚いたこと

最近(ようやくというべきか)Python を触っているんだが、他言語に慣れた人がびっくりする Python の特徴。

関数の返り値が複数取れる

多くの人が驚くのがこれだと思う。

y, z = object.function()

というような書き方が平然とまかり通っている。

関数側の実装を

def test()
    return 'test', 100

とすれば(もちろん、こういう書き方が文法的に許される、という前提が必要だが)、 確かに返り値は複数になりますね。

// が切り捨て除算

これはワイだけかな?

最初見たとき、「え、これ # のタイポ?」と思った。

“”” コメント “””

複数言語を使う際に注意払うのがコメントアウトだと思うが、quot 記号3連続というのは斬新でした。

if __name__ == ‘__main__’ の謎

サンプルでよくある

if __name__ == ‘__main__'

という書き方だが、ここあたり参照。

要は、「ある python コードをコマンドラインから実行するときは __name__ に __main__ が引き渡される」というだけの話だと思う。

これをすごくわかりにくく書いてあったり、インデントがおかしいサンプル載っけてたりする記事をよく見かけるが、なんだあれ?

 

(続く)

 

librosa の使い方

python の音響解析の基本的なモジュールは librosa のようなんだが、情報が古い記事が多い。

参考記事

比較的しっかり書かれた記事は

Pythonの音声処理ライブラリ【LibROSA】で音声読み込み⇒スペクトログラム変換・表示⇒位相推定して音声復元

あたりだろうか。

上の記事にしても 2020 年に書かれているので、やや古いのだが、解説が丁寧だし、検証がまとも。

「古い」というのは、例えば、librosa.display.waveplot などという今では廃止になった関数もしれっと使われていたりするから。
だが、こういうのはエラーメッセージをググるなどすれば、対策は立てやすい。(後継の関数を使うならば、librosa.display.waveshow となる)

参考にならない記事

逆に、古いだけで、処理の意図も書かれていない記事はほぼ害悪。

例えば、STFT(Short-Time Fourier Transform: 短時間フーリエ変換)で出力される行列は、確か 0.8 くらいの時に出力形式そのものが変わったのだが、このことを意識していないと実用(波形データ→STFT→加工→逆 STFT →加工した波形データなど)に使う際に間違ったことをやりねない。

SoundFile との併用

便利な librosa だが、配列をファイルに書き出そうとすると使い勝手が悪いらしい。
(『librosa における 16-bit でのWAV書き出し』参照)

そこにもあるように SoundFile というパッケージを使う。

なお、書き出したファイルは(デフォルトでは)python ファイルのある位置になるようだ。
jupyter notebook の場合は、.ipynb の置かれているフォルダになる。

noisereduce との併用

実際のデータはノイズなどものっているため、これを落とす処理が必要。
その一つに noisereduce パッケージがある。なお、これも librosa 同様、conda ではなくて pip から入れる。

これを使った具体例は、ここあたり参照。

 

3D可視化

波形データの可視化は、上のようにやればいいが、ものによっては3Dで可視化したい場合がある。

pyVISTA というのがあるらしい。

pythonをインターフェイスにして VTK を呼び出して・・・ってこれ MacOS じゃダメじゃん。
VTK は OpenGL に依存しているので、MacOS では非推奨の VTK 使ってる時点でアウト。

日本からは小山哲央という人が参加しているらしいのだが、この人ってやらかしちゃった人ですよね。

スクリプト言語が関の山で、Java/C/C++ 使いこなす能力はないでしょう、この人。

将来性、イマイチ。