スキップしてメイン コンテンツに移動

投稿

ラベル(C)が付いた投稿を表示しています

プログラミングができるということは、人生を楽にできるということ

どのようにプログラマになったのか知るのは面白く、興味深い。そして、プログラミングに対するの様々な考え方や捉え方を知り、それを自分の知識に加えるのが楽しい。そこで自分自身がどのようにしてプログラミングを学んできたのか、そしてプログラミングについてどのように考えているのか述べたいと思う。たいした内容でもないが書いているうちに長文になってしまった。取り敢えず概要だけを知りたい方は、それぞれの段落の最初の文章を読めば何となく分かると思う。 最初のプログラミング 初めて触れたPCは、父親が購入したNECのPC-8801mkIIだった。当時のPCには最初からBASIC(N88-BASIC)が付属しており、これを使って手軽にプログラミングができた。ただ、BASICはインタプリタであり、当時のPCの性能と相まって処理速度が非常に遅く、高速化のためにはアセンブラが必要だった。そこで、父親の書籍を漁りつつ、ニーモニックをハンドアセンブルでマシン語に変換したりもした。 しかしながら、BASICのプログラミングですら当時の自分にとってはとても難しく感じた。その頃読んでいたマイコンBASICマガジンに載っていたプログラムのようなコンパクトでエレガントなコードに比べ、自分のコードはなんて拙くて汚いのだろうと何度も思ったものだ。結局、プログラミングはセンスがある人だけのもので、自分には無理なのだろうかとさえ考えた。あるアイデアがあっても、それを思ったようにコードに落とせないのだ。毎回リファレンスとのにらめっこになる。それでも何とか書き上げたコードは不必要に肥大であり、分かりにくく、あちこちからバグが顔を出していた。 それでも、不細工だろうが何だろうが、プログラムが完成するのはとても嬉しいことだった。自分の力でゼロから何かを作り上げるという行為は本当に楽しかった。後に気が付いたことだが、プログラミングはセンスなんかよりも、この「楽しい」という気持ちの方がよほど重要だったのだ。センスがあっても楽しくなければ長くは続かないだろうし、楽しいと思っているのならば小さな積み重ねが経験となり、知識となっていく。 ただ、その当時はプログラミングばかりをしていたわけではなかった。PCゲームにもはまっていた。オールマシン語が売り文句の一つだった頃だ。そして、その頃のゲームソフトは個人もしくは少数...

C言語: ポインタと配列について

C言語の入門書を読み始めたばかりの初心者である学生が、先生に質問をしているようです。どんな内容かちょっと聞いてみましょう。 ポインタの使い方 学生 : 先生、ポインタの宣言ってこれで合っていますよね。 int *p; でも p[0] = 5; のように使うとエラーが出るんです。 先生 : それは当たり前だ。まだメモリを確保していないだろう? メモリを確保しないと使えないぞ。 学生 : でも配列の場合、 int a[10]; のように宣言したら使えましたよ。ポインタって全然ダメですね。 先生 : ポインタと配列はまったく違うものだから、どちらがダメでどちらが良いと云うことはないよ。配列はその場でメモリを確保して使えるが(上記の場合はintを10個分)、プログラム内で決め打ちした数値になってしまう。つまり後から大きさの変更ができないんだ。 一方、ポインタは宣言しただけでは使えないが、後から自由にメモリを割り当ててその大きさを変えられるから、少ないメモリで済む場合は小さく、たくさんのメモリが必要な場合は大きくメモリを自由に確保することができる。 学生 : へぇ。ところで、どうやってメモリを確保すればいいんですか? 先生 : それはこのようにすればいい。 p = (int*)malloc(10 * sizeof(int)); これは、intのポインタにintの大きさで10個分のメモリを割り当てるという意味だ。確保したメモリが不要になったなら free(p); で開放する。この辺についてはその入門書にも載っているだろう。 学生 : ふ~ん。なんだか難しそうですね。 先生 : 慣れれば難しいことなどないよ。C言語なら malloc でメモリを割り当て、 free で開放と覚えていればいい。 学生 : 分かりました。早速使ってみます。 ポインタと配列の違い 学生 : ポインタを使ってみましたが、結局、中の数値を見たり変更したりする場合は、 p[2] のように配列の形にしないといけないんですよね。なんだか混乱するなぁ。 先生 : 別に p[2] ではなくても *(p+2) としても良いぞ。どちらもまったく同じ意味だ。配列型と同じように見えるからといって配列ではないからその点は気をつけることだな。因みに、...

C/C++でポインタによる多次元配列を連続したメモリ領域に作成する

下記のようにC/C++の配列で多次元配列を作れば連続したメモリ領域となるが、動的に大きさを変えられないし、関数に渡したりするのも大変だ。 int a[N][M]; 一方、ポインタを使った下記の方法だと確保したメモリ領域が不連続となる。 int **a = new int*[N]; for (int i = 0; i < N; i++) a[i] = new int[M]; 動的にメモリ確保して連続したメモリ領域にしたい場合、以下のようにすれば良い。 int **a = new int*[N]; a[0] = new int[N * M]; for (int i = 1; i < N; i++) a[i] = a[0] + i * M; ここで、 a[i][j] と (*a)[i*M+j] は同じ値を示す。 二次元配列、三次元配列を扱った実際のコード(C/C++)を最後に載せておく。 Cバージョン: #include <stdio.h> #include <stdlib.h> /* NX*NYの二次元配列およびNX*NY*NZの三次元配列. */ #define NX 5 #define NY 6 #define NZ 7 int main() { int **x2; int ***x3; int cnt2 = 0; int cnt3 = 0; int i, j, k; /* 二次元配列の作成 */ x2 = (int**)malloc(NX * sizeof(int*)); x2[0] = (int*)malloc(NX * NY * sizeof(int)); for (i = 1; i < NX; i++) x2[i] = x2[0] + i * NY; /* 三次元配列の作成 */ x3 = (int***)malloc(NX * sizeof(int**)); x3[0] = (int**)malloc(NX * NY * sizeof(int*)); x3[0][0] = (int*)malloc(NX * NY * NZ * sizeof(int)); for...

インデントの大切さ

大学の実習で学生にプログラミングを教えているのだが、C言語などでプログラムを書かせるとほとんどの人がインデント(字下げ)をでたらめに入れる。これは何故だろう。 教えている学生は2年生なのだが、1年生で既に講義で習っているはずなのに。自分ならインデントを揃えないと読みづらくてやっていられなくなるが、インデントをでたらめに書く人はそれを苦労とは思わないのだろうか。インデントの意味を教えてもキツネにつままれたような顔して、それが重要であると考えてくれないようなのだ。それならばと放っておくと括弧が足りなくてコンパイルエラー、条件文のスコープが分からなくなって四苦八苦、関数を終わらせずに次の関数を書き始める等々、奇妙奇天烈なコードを書いてくれる。で、最後には分からなくなったので教えて欲しいと言ってきて、インデントがでたらめなコードを自分が読む羽目になるのだ。 プログラムを書くなら、文法などよりもまずインデントの重要性を知る必要があると思う。…と考えるとやっぱりPythonはプログラミングを習い始めるのに良い言語のように思うなぁ。

C言語で複数のアルゴリズムを使って円周率を求める

以前、大学でプログラミング実習を教えたときの課題だった、複数のアルゴリズムで円周率を求めるプログラムが出てきたのでここに書いておく。実行時間も測定している。これはCで書いたけど、使用言語はFortranでも可だったはず。 出力結果: Machin's formula: 3.14159265358979 Time: 0.437000s (1000000 times) Gauss-Legendre : 3.14159265358979 Time: 0.219000s (1000000 times) Count points : 3.14552000000000 Time: 0.265000s (10 times) Monte Carlo : 3.14394000000000 Time: 0.594000s (10 times) Circumference : 3.14159265358979 Time: 3.047000s (1000000 times) Integral : 3.14159265393433 Time: 0.375000s (10 times) Machin's formula, Gauss-Legendre, Circumferenceは100万回計算、Count points, Monte Carlo, Integralは10回計算して時間を測定している。それにしても、算術幾何平均を使ったガウス-ルジャンドルのアルゴリズムは速いなぁ。 以下、ソースコード。 /*======================================================= ratio of the circumference of a circle to its diameter =======================================================*/ #include <stdio.h> #include <stdlib.h> #include <math.h> #include <time.h> /*-...

Duff's device

仕事で使うコードの最適化について考えていたら、ふと、Duff's deviceが思い浮かんだ。仕事で使うコードとはまったく関係ないんだけど。 Duff's deviceは以下のようなコードである( Wikipedia英語版 から若干修正)。 void strcpy(register char *to, register char *from, register count) { register n = (count + 7) / 8; switch (count % 8) { case 0: do { *to = *from++; case 7: *to = *from++; case 6: *to = *from++; case 5: *to = *from++; case 4: *to = *from++; case 3: *to = *from++; case 2: *to = *from++; case 1: *to = *from++; } while (--n > 0); } } 要は、switch文のfall-throughを利用したループ展開による効率の良いメモリコピーだ。因みに、上記のコードで to に対してインクリメントがされていないが、出力レジスタへのコピーなのでこれで正しい。 効率的だといってこのコードを普段から利用するのはあまり良いとは云えないと思う。可読性が良いわけではないし、最近のコンパイラは最適化が進んでいることもあり、場合によっては速くならないことも考えられる。個人的には、このコードはswitch文におけるfall-throughの端的な例として見せるに留めるのが良いように思う。

GCCでSSEを使ったプログラミング

先日、GCCでIntelのSSEを使う話が出たのでメモ。 MMX: mmintric.h SSE: xmmintric.h SSE2: emmintric.h SSE3: pmmintric.h SSSE3: tmmintric.h ついでにモトローラのAltiVecでは以下の通り。 AltiVec: altivec.h 上記をインクルードすればそれぞれで固有の命令が使える。何れにしろ、SIMDのプログラミングは面倒。因みに、PS3ではaltivec.hを利用してプログラミングしていくことになる。

C99におけるrestrictポインタの存在意義

ベクトル型スーパーコンピュータでは必要不可欠。ポインタをrestrictで修飾することで、そのポインタは別名を持たない、つまり、どの変数からも参照されないことが保証される。なので、ベクトル・プロセッサやSIMD演算での最適化を効率よく行うことができる。因みに、C99以前でも拡張仕様でrestrictは使われていた。 尚、restrictについては、 プログラミング言語 C の新機能 - restrictポインタ での説明が詳しい。

C言語で実行時間測定

C言語で実行時間を測定する方法。覚書。正確さから言えばgettimeofdayかな。手軽さからならclockあたりで。Unixであれば、timeコマンドがソースの変更もなく手間もかからず簡単。Windowsの場合は、 timeitコマンド が リソースキット に含まれている。 % time 実行ファイル 以下、Cのコード。 clock #include <stdio.h> #include <time.h> int main() { clock_t t1, t2; t1 = clock(); /* 処理. */ t2 = clock(); printf("%f\n", (double)(t2 - t1) / CLOCKS_PER_SEC); return 0; } gettimeofday #include <stdio.h> #include <time.h> #include <sys/time.h> double gettimeofday_sec() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec + tv.tv_usec * 1e-6; } int main() { double t1, t2; t1 = gettimeofday_sec(); /* 処理. */ t2 = gettimeofday_sec(); printf("%f\n", t2 - t1); return 0; } getrusage #include <stdio.h> #include <time.h> #include <sys/time.h> #include <sys/resource.h> double getrusage_sec() { struct rusage t; struct timeval tv; getrusage(RUSAGE_SELF...