C言語の復習その4。enum,3項演算子,goto,関数
関数ポインタをちょっと読み込んだくらい進みましたが、 関数ポインタは難しそうなので、それで一つ記事を書きます。
というわけでキリが良いので投稿♪
enum
- enumのメンバは本来値を持たない。(持つべきではない)
- 実は数値型の値を持っている。
- 先頭から0,1,2,3...と割り振られている。
typedef enum { SUCCESS, //0 FAILURE //1 }Result;
- ただしこの値を直接計算・表示に使うのはenumの利用方法としてはふさわしくない。
- K&Rにこの「ふさわしくない利用例」が乗ってしまっているらしい。
- 確かJavaのenumも内部ではintか何かだったが、内部の数値を直接利用できなかった気がする。
- 残念ながら本来想定されていた用途で利用されていることは少ないらしい
- C言語の仕事をしたことがないので分かりません。
typedef enum { SUCCESS,FAILURE }Result; /** * print divided result. * * @param dividend [description] * @param divisor [description] * @return [description] */ Result divide(int dividend,int divisor){ if(b==0){ return FAILURE; } printf("calc result is %d\n",dividend / divisor); return SUCCESS; } int main(){ Result result = divide(6,0); if(result == FAILURE){ printf("divide is fail\n"); } //enumのメンバは数値を割り当てられているが、それを表示したり処理に使うのはenum本来の使い方ではない。 printf("success num is %d\n",SUCCESS); //0 printf("failure num is %d\n",FAILURE); //1 }
- OSのコードとして使うなら例外の種類を表現したりするのに使えるのではないかなと思いました。
- トラップ
- フォールト
- アボート
typedef enum { trap,fault,abort }Exception;
コメントの指摘
Kaz Mu (id:fa11enprince)さんからこんな指摘をいただきました。
OSを作る、読むという目標から、あまり重要でないと思い検証していなかったのですが、 せっかくコメントを頂いので検証してみます。
本書ではenumは整数型である必要はあるが、int型である必要は無いと明記されていました。
C言語の使用には「enum型の値は、整数としても使える」とはっきり定められています 注)これはあくまでも「整数」であって、「int」では無いことに注意してください。本書に付属のgccでは,enum型はこの部分はかなり合理的に最適化することができ、定数の数が255以下の場合には1バイト、それ以外の場合には2バイト取られるようにするオプションが用意されています。
少し調べたところ、-fshort-enums
というオプションがそれのようです。
http://www.keil.com/support/man/docs/armclang_ref/armclang_ref_chr1411640303038.htm を参考に検証するコードを書いてみました
enum defaultEnum{test,test1,test2}; // Largest value is 8-bit integer enum int8Enum {int8Val1 =0x01, int8Val2 =0x02, int8Val3 =0xF1 }; // Largest value is 16-bit integer enum int16Enum {int16Val1=0x01, int16Val2=0x02, int16Val3=0xFFF1 }; // Largest value is 32-bit integer enum int32Enum {int32Val1=0x01, int32Val2=0x02, int32Val3=0xFFFFFFF1 }; // Largest value is 64-bit integer enum int64Enum {int64Val1=0x01, int64Val2=0x02, int64Val3=0xFFFFFFFFFFFFFFF1 }; //コンパイルできず //enum int128Enum{test3=0x01,test4=0x02,int128Val=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1} /* chap2/enum.c:22:49: error: integer literal is too large to be represented in any integer type enum int128Enum{test3=0x01,test4=0x02,int128Val=0xFFFFFFFFFFFFFFFFFFFFFF... */
printf("size of default is %zd\n", sizeof (enum defaultEnum)); printf("size of int8Enum is %zd\n", sizeof (enum int8Enum)); printf("size of int16Enum is %zd\n", sizeof (enum int16Enum)); printf("size of int32Enum is %zd\n", sizeof (enum int32Enum)); printf("size of int64Enum is %zd\n", sizeof (enum int64Enum)); //printf("size of int128Enum is %zd\n", sizeof (enum int128Enum));
$ gcc chap2/enum.c $ ./a.out size of default is 4 size of int8Enum is 4 size of int16Enum is 4 size of int32Enum is 4 size of int64Enum is 8 $ gcc -fshort-enums chap2/enum.c $ ./a.out size of default is 1 size of int8Enum is 1 size of int16Enum is 2 size of int32Enum is 4 size of int64Enum is 8
検証の結果から手元のapple clangでは
- enumのメンバはデフォルト4バイト
- 4バイトで表現できない場合は8バイトになる
-fshort-enums
オプションをつけるとなるべく小さい値でenumをコンパイルする- 値の代入がなければ4バイトが割り振られる
ということがわかりました。
3項演算子
int a =-1; printf("%d\n",a); //入れ子にできる char *judge_result = 0!= (a>0? a: 0)? "正の数":"0か負の数"; //奇妙にみえるが以下と同じ意味 //int fixed_number = a>0? a: 0; //char *judge_result = 0!= fixed_number ? "正の数":"0か負の数"; printf("%s\n",judge_result);
3項演算子は多分、式なので入れ子にできます。 可読性が無くなりますが。
- 個人的に3項演算子と言うネーミングはいまいちしっくりこない。
- if式と言う名前ならしっくりきてたかな。
- Kotliのwhen式みたいな感じ
- ?式、q式とかでもいいかな?
- 逆にわかりにくくなってしまった。
- osdev-jpで教えてもらいましたが、3項演算子というのは俗称で、JIS C99 規格書 § 6.5.15*1 では条件演算子と書かれているそうです
GOTO
- gotoが有用な場合もある
- 例えば2重ループから抜ける場合
- 以下サンプル
/** * 九九を計算して表示する * 引数limitの数字に到達した時点で処理をやめる * @param limit [description] */ void nine_nine(int limit){ for(int i=1;i<10;i++){ for(int j=1;j<10;j++){ int result = i*j; printf("%d\t",result); if(result==limit){ goto last; //2重ループを一気に抜ける } } printf("\n"); } last: printf("\n"); //ちなみにこの1行を抜くとコンパイルエラーになる }
このコードをgotoをつかわずに書くとこんな感じになります。
/** * 九九を計算して表示する * 引数limitの数字に到達した時点で処理をやめる * @param limit [description] */ void nine_nine(int limit){ for(int i=1;i<10;i++){ int result; for(int j=1;j<10;j++){ result = i*j; printf("%d\t",result); if(result==limit){ break; } } printf("\n"); if(result==limit){ break; } } printf("\n"); }
- まぁ抜けたい分のループだけ別の関数に括り出して
return
すると言うのもアリですが。 疑問:GOTOのラベルは関数ブロックの最後尾にかけない?
gotoが移動できるのは同じ関数の中だけ。
- near jumpみたいだなと思いました。
$ gcc chap4/control.c chap4/control.c:25:1: error: expected statement } ^
関数
気になる記述があったのでメモ。
実際には以下のようなことがあることは、実務を行なっているC言語プログラマでも知らない人もいるようです。 呼び出し側は返値は受け取らなくても良い 返値を返さない関数も定義できる 返値の型を省略できる場合もある 返値を受け取る側の変数と、返値の肩は必ずしも一致していなくても良い
P204より。C言語の仕事をしたことがないので(以下略)
- printfは数値を返している
- 成功した場合は表示した文字数を
- 失敗した場合は-1を
int num = printf("test\n"); printf("%d\n",num);//5
- 値を返さないのに関数?
数学的に考えれば、「関数」と言うのは、必ず結果(つまり返値)が導かれるものです。
- この話は非常に納得がいく。
ちょっと昔、こういう細かい言い方にこだわってて関数っぽいものは以下のように言い分けるように心がけている
- サブルーチン:戻り値を返さないこと
- コマンドクエリ分離の原則(CQRS)でいうコマンドに対応するもの
- メソッド:メンバに対する処理(参照更新代入等)があること
- 関数:値を返すこと、副作用がないこと
- サブルーチン:戻り値を返さないこと
int型の関数は型の宣言を省略できる
- 引数も省略できる
test(a){ return a+3; } ・・・ printf("%d\n",test(5));//8
$ gcc --version Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1 Apple LLVM version 9.1.0 (clang-902.0.39.2) Target: x86_64-apple-darwin17.7.0 Thread model: posix InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin $ gcc chap5/function.c chap5/function.c:8:17: warning: implicit declaration of function 'test' is invalid in C99 [-Wimplicit-function-declaration] printf("%d\n",test(5)); ^ chap5/function.c:14:1: warning: type specifier missing, defaults to 'int' [-Wimplicit-int] test(a){ ^ 2 warnings generated.
main関数の型をvoidにするのは間違い
- C言語の仕様ではない。
int main(int args,char *argv[])
がより良い。
externとstatic
- externは他のファイルから呼び出せる
- staticは呼び出せない。
- extern,staticの指定がない場合はexternになる。
- C++ではstaticになるらしい?
- 関数のextern,staticはだいたい上記の認識でいいが変数は別。
C言語能力が着実にレベルアップしています。かなり良い本だと思います。
プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み
- 作者: 倉薫
- 出版社/メーカー: 翔泳社
- 発売日: 2009/02/13
- メディア: 大型本
- 購入: 2人 クリック: 6回
- この商品を含むブログ (3件) を見る
プログラマのためのSQL 読書会(22)に参加
26.7から27.1.3まで読みました。
p478の複雑なクエリの解説
基底テーブル
--▼P.476 CREATE TABLE AnnualSales1 (salesman CHAR(15) NOT NULL PRIMARY KEY, jan DECIMAL(5,2), feb DECIMAL(5,2), mar DECIMAL(5,2), apr DECIMAL(5,2), may DECIMAL(5,2), jun DECIMAL(5,2), jul DECIMAL(5,2), aug DECIMAL(5,2), sep DECIMAL(5,2), oct DECIMAL(5,2), nov DECIMAL(5,2), "dec" DECIMAL(5,2)); -- decは予約語のため、ダブルクォートが必要
イメージとしては1~12月の売上とセールスマンの情報をペアに持つテーブルになります。 とりあえずこのテーブルに入るデータを用意します。
投入データ(オリジナル)
insert into AnnualSales1 (salesman,jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,"dec") values ('yabu',200.2,190.3,220.21,240.12,220.65,510.78,190.89,220.11,230.43,210.32,190.43,480.23)
1個目のビュー
--▼P.477 CREATE VIEW NumberedSales AS SELECT salesman, 1 AS M01, jan, 2 AS M02, feb, 3 AS M03, mar, 4 AS M04, apr, 5 AS M05, may, 6 AS M06, jun, 7 AS M07, jul, 8 AS M08, aug, 9 AS M09, sep, 10 AS M10, oct, 11 AS M11, nov, 12 AS M12, "dec" FROM AnnualSales1;
2個目のビュー
--▼P.478 CREATE VIEW AnnualSales2 (salesman, month, sales_amt) AS SELECT S1.salesman, (CASE WHEN A.nbr = M01 THEN 'Jan' WHEN A.nbr = M02 THEN 'Feb' WHEN A.nbr = M03 THEN 'Mar' WHEN A.nbr = M04 THEN 'Apr' WHEN A.nbr = M05 THEN 'May' WHEN A.nbr = M06 THEN 'Jun' WHEN A.nbr = M07 THEN 'Jul' WHEN A.nbr = M08 THEN 'Aug' WHEN A.nbr = M09 THEN 'Sep' WHEN A.nbr = M10 THEN 'Oct' WHEN A.nbr = M11 THEN 'Nov' WHEN A.nbr = M12 THEN 'Dec' ELSE NULL END), (CASE WHEN A.nbr = M01 THEN jan WHEN A.nbr = M02 THEN feb WHEN A.nbr = M03 THEN mar WHEN A.nbr = M04 THEN apr WHEN A.nbr = M05 THEN may WHEN A.nbr = M06 THEN jun WHEN A.nbr = M07 THEN jul WHEN A.nbr = M08 THEN aug WHEN A.nbr = M09 THEN sep WHEN A.nbr = M10 THEN oct WHEN A.nbr = M11 THEN nov WHEN A.nbr = M12 THEN "dec" ELSE NULL END) FROM NumberedSales AS S1 CROSS JOIN (VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12)) AS A(nbr);
- select * from AnnualSales1
salesman | jan | feb | mar | apr | may | jun | jul | aug | sep | oct | nov | dec |
---|---|---|---|---|---|---|---|---|---|---|---|---|
yabu | 200.20 | 190.30 | 220.21 | 240.12 | 220.65 | 510.78 | 190.89 | 220.11 | 230.43 | 210.32 | 190.43 | 480.23 |
- select * from NumberedSales;
salesman | M01 | jan | M02 | feb | M03 | mar | M04 | apr | M05 | may | M06 | jun | M07 | jul | M08 | aug | M09 | sep | M10 | oct | M11 | nov | M12 | dec |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
yabu | 1 | 200.20 | 2 | 190.30 | 3 | 220.21 | 4 | 240.12 | 5 | 220.65 | 6 | 510.78 | 7 | 190.89 | 8 | 220.11 | 9 | 230.43 | 10 | 210.32 | 11 | 190.43 | 12 | 480.23 |
- select * from AnnualSales2;
salesman | month | sales_amt |
---|---|---|
yabu | Jan | 200.20 |
yabu | Feb | 190.30 |
yabu | Mar | 220.21 |
yabu | Apr | 240.12 |
yabu | May | 220.65 |
yabu | Jun | 510.78 |
yabu | Jul | 190.89 |
yabu | Aug | 220.11 |
yabu | Sep | 230.43 |
yabu | Oct | 210.32 |
yabu | Nov | 190.43 |
yabu | Dec | 480.23 |
イメージ的には
水平展開されている規定テーブルを縦横変換する感じでしょうか。
p479のクエリについて
自宅で帰って動かしたところ、PostgreSQL 10では動作しませんでした。
SELECT * FROM ((SELECT x FROM Foo) --このサブクエリの結果に名前を付ける必要あり LEFT OUTER JOIN (SELECT x FROM Bar) --このサブクエリの結果に名前を付ける必要あり ON Foo.x = Bar.x) AS Foobar(x1, x2) INNER JOIN Floob ON Floob.y = x1;
以下のように変えると動きました。
with foo(x) as (values(1),(2),(3)), bar(x) as (values(1),(4),(3)), floob(x,y) as (values(1,1),(9,2)) --↑共通テーブル式でクエリ実行に必要なテーブルを宣言しています。 SELECT * FROM ((SELECT x FROM Foo) as foo --修正 LEFT OUTER JOIN (SELECT x FROM Bar) as bar --修正 ON Foo.x = Bar.x) AS Foobar(x1, x2) INNER JOIN Floob ON Floob.y = foobar.x1;
結果
x1 | x2 | x | y |
---|---|---|---|
1 | 1 | 1 | 1 |
2 | 9 | 2 |
そもそもの私の疑問はこれの一つ上の「まだ間違い」とコメントされているクエリの 結合結果に別名を付け足せれば動くのでは?というものでした。
SELECT * FROM (Foo AS F1 LEFT OUTER JOIN Bar AS B1 ON F1.x = B1.x) as Foo(x,y) --この行を付け足しています! INNER JOIN Floob ON Floob.y = Foo.x; -- まだ間違い
私が付け足した行で正しくうごきました。
x1 | x2 | x | y |
---|---|---|---|
1 | 1 | 1 | 1 |
2 | 9 | 2 |
結果も本書で正しい、とされている最終のクエリと同じものになりました。*1
MySQLと艦これ
昔艦これでMySQL Clusterのエラーメッセージが出ていたらしい。
http://nice-boat.jp/archives/46
- 最近のMySQLでは全てのエラーにエラーコードがついた。
- エラーコードといえば。。。
と言う文脈でこの話題が出てきました。
そもそもエンドユーザーにはミドルウェアのエラーメッセージは出さないのが基本です!!!
https://www.ipa.go.jp/security/awareness/vendor/programmingv1/b09_03.html
一時テーブル
create global temporary table などはこの本が出た後にSQL標準に入ったかも? という話だが、 「create global temporary table iso」などでググっても それらしいものが出なかった。
- 一時表はmysqlはクエリのチューニングでよく使う
アッカーマン関数
- 再帰の説明に出てきてなにこれ?となった
与える数が大きくなると爆発的に計算量が大きくなるという特徴があり、性能測定などに用いられることもある。
だそうです。
郵便番号はユニークではない。
https://qiita.com/_takwat/items/3a121656425fac7bb820
事態は結構深刻だと思う。
- 郵便番号は必ず1つの町名に紐づいているわけではない
- 市区町村をまたいで同じ郵便番号を持つケースがある
市区町村はおろか県を飛び越えて同じ郵便番号を持ちうるケースがある
まぁ郵便番号をナチュラルキーにするのはやめましょうということですね。
- ナチュラルキー事態があまり好きではありません。
UNION互換
行式、テーブル式の結果の変数型と順番が同じこと。
UNION 互換とは、以下の二つの条件を共に満たすことです。 1.列の数が同一であること 2.同じ列位置の列のデータ型が同じ(または自動的に型変換可能)であること
http://www.geocities.jp/mickindex/database/celko/celko_so.html
ミックさんのサイトより引用。
和両立ともいう
和両立(union compatible)とは、2つの関係において、次数(属性数/列数)が等しく、対応する属性同士のドメイン(値の型・範囲)が等しく、2つの関係の型が適合している様子をいいます。次の例は和両立である2つの関係です。
https://www.db-siken.com/kakomon/21_haru/am2_8.html
デスペの過去問解説から引用。
一応本書にも34章に解説がある😅
これはイカの全ての条件を満たすことを言う。 1.2つのテーブルが同じ列数である 2.同じ位置の隠れつは同じデータ型(または自動的な変換が可能)である。
本書34賞より引用。 ミックさんのサイトのものとほぼ同じですね。
この本後半で解説するものを断りもなく前半でバンバン出し過ぎな気がする。
誤植
p.488 ZipCodeにcity列がない
▼P.488 CREATE TABLE ZipCodes (state_code CHAR(2) NOT NULL PRIMARY KEY, low_zip CHAR(5) NOT NULL UNIQUE, high_zip CHAR(5) NOT NULL UNIQUE, CONSTRAINT zip_order_okay CHECK(low_zip < high_zip)); --- SELECT A1.name, A1.street, SZ.city, SZ.state_code, A1.zip FROM ZipCodes AS SZ, AddressBook AS A1 WHERE A1.zip BETWEEN SZ.low_zip AND SZ.high_zip;
p489 Address.zip => AdressBook.zipでは?との指摘がありました。
▼P.489 SELECT name, street, city, state, zip FROM StateZip2, AddressBook WHERE state = (SELECT state FROM StateZip2 WHERE high_zip = (SELECT MIN(high_zip) FROM StateZip2 WHERE Address.zip <= StateZip2.high_zip));
スモールワールド
参加者の方、実は同じ建物で働いている人でしたwこの業界意外と狭い!
- 作者: ジョー・セルコ,Joe Celko,ミック
- 出版社/メーカー: 翔泳社
- 発売日: 2013/05/24
- メディア: 大型本
- この商品を含むブログ (16件) を見る
*1:急に出てきたサブクエリが中途半端だな...と読んでて思ったのです。
C言語復習その3構造体、共用体について
構造体と共用体について学びました。
構造体のサイズについて
Bitフィールド
- char(1Byte)より小さい長さで構造体のメンバを分割
- 1bit,2bitの長さの型はないのかと常に疑問だったのでこれは嬉しい。
- 低レイヤー勢は使いまくってそう。
- 1Byte以下の長さをもつレジスタの分割なんかはこれを利用するといいんじゃないかなと思いました。
- 型はint,unsigned int,signed intのみ
- メンバの長さが中途半端だとアドレスをまたぐことがありえる
- 名前なしのビットフィールドを活用してワードアライメント的な埋めを開発者が設定できる。
- bitフィールドのアドレスは参照できない。
- バイトの単位でアドレスは振られる
- ビットフィールドはバイトより小さな長さを持ち得るのでアドレスが割り振れない
- そのメンバの開始地点のアドレスくらいわかってもいいんじゃないかな〜と思った。
- バイトの単位でアドレスは振られる
typedef struct{ unsigned al:8; unsigned ah:8; } registers; ・・・ //bitフィールドのメンバのアドレスは参照できない。 //chap2/struct.c:37:30: error: address of bit-field requested printf("ah adress is %p\n",®isters.ah); //bitフィールドを使った構造体のアドレスは取得可能。 printf("registers adress is %p\n",®isters);
共用体
- ある値の何バイト目まで、というような値の参照ができる。
- 見た瞬間にCPUのレジスタを扱うのに向いているのでは?と思った。
- hikaliumさんが過去に作られていたOSで適当にunionとググってみたところcpuのレジスタのbitを共用体で表現しているところがありました。
- EFLAGSやCRnレジスタはnビット目がこれ!という表現を共用体を使わないと混乱すると思います。
- Linuxでも共用体でレジスタっぽいものを表現していました。*1
- たぶんCPUのレジスタを共用体で表現するということは普通に行われていることなのでしょう。
- 本書ではほとんど使われない、と書かれているが、ある程度低レイヤーの分野の書籍を読んだことがあると用意の活用イメージが湧く。
確かに面白いのですが、実際のところ普通のプログラミングをしている限り、ほとんど用途がありません。ですから、こういうものがある、ということだけ覚えておけば十分でしょう。
- COBOLに似たような機能があった気がする。レベル変数?とか言った気が。
eaxを共用体で表現
- eat ax ah alを共用体で作って見ましたが。。。
#include <stdio.h> typedef struct{ //unsigned al:8; unsigned char al; //unsigned ah:8; unsigned char ah; } ax_8bit; typedef union { //unsigned ax:16 short ax; ax_8bit _8bit; } ax; typedef union{ //unsigned eax:32 int eax; ax axpart; }eax; }
出力
eax is 0x1d2c65b8 ax is 0x 65b8 al is 0x b8 ah is 0x 65 eax size is 4 ax size is 2 ah size is 1 al size is 1 eax addr is 0x7ffeec3b9ae0 eax addr is 0x7ffeec3b9ae0 ax addr is 0x7ffeec3b9ae0 ah addr is 0x7ffeec3b9ae1 al addr is 0x7ffeec3b9ae0 0x7ffeec3b9ae0 is b8 0x7ffeec3b9ae1 is 65 0x7ffeec3b9ae2 is 2c 0x7ffeec3b9ae3 is 1d
0x7ffeec3b9ae0
をベースアドレスにして整理すると
offset | 0 | 1 | 2 | 3 |
---|---|---|---|---|
value | b8 | 65 | 2c | 1d |
8bit r | [al] | [ah] | - | - |
16bit r | [ax | ax] | - | - |
32bit r | [eax | eax | eax | eax] |
下位アドレス、上位アドレスなどの並びがちょっと混乱してわからない。これあってるのかな?
プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み
- 作者: 倉薫
- 出版社/メーカー: 翔泳社
- 発売日: 2009/02/13
- メディア: 大型本
- 購入: 2人 クリック: 6回
- この商品を含むブログ (3件) を見る
*1:ネットワークのパケット周りでも使われている
C言語の復習その2ポインタ
- 各型に対するポインタ変数の型がある
- どのかたのポインタも同じサイズ(私の環境では8バイト)
- 構造体のポインタ型もある
- ポインタ変数型のポインタもある
char c = 'c'; char* pc = &c; char **ppc = &pc; printf("-------------------\n"); printf("c value is %x\n",c); //63 = asciiで'c' printf("pc value is %x\n",pc); //ec1daabf printf("ppc value is %x\n",ppc); //ec1daab0 printf("-------------------\n"); printf("c addr(&c) is %x\n",&c); //ec1daabf printf("pc addr(&pc) is %x\n",&pc); //ec1daab0 printf("ppc addr(&ppc) is %x\n",&ppc); //ec1daa98 printf("-------------------\n"); printf("c reference is nothing\n"); //なし printf("pc reference(*pc) is %x\n",*pc); //63 printf("ppc reference(*ppc) is %x\n",*ppc); //ec1daabf printf("-------------------\n"); printf("ppc referencese reference(**ppc) is %x\n",**ppc);//63
この例を表に起こすとこんな感じ
変数 | 型 | 値 | アドレス | ポインタ参照(*変数) | ポインタ参照の参照(**変数) |
---|---|---|---|---|---|
c | char | 0x63 | 0xec1daabf | なし | なし |
pc | char* | 0xec1daabf | 0xec1daab0 | 0x63 | なし |
pcc | char** | 0xec1daab0 | 0xec1daa98 | ec1daabf | 0x63 |
大きな配列をローカル変数として宣言するとスタックを使い過ぎてしまう。
C言語では関数には値そのものではなく、値のコピーが渡るらしい。
- 逆アセンブラ(objdump)で見て見たけど、よくわからなんかった
- 配列を渡すと、配列のアドレス(のコピー)が渡る
- 配列のコピーを渡す方法はない。
ポインタの正確な定義は難解らしい
ポインタという言葉の定義は、実はあんまりはっきりとは決まってないらしい 注2-A正確にいうとANSIの仕様として正確に決まってはいます。が、それを定めた文章はあまりに難解で、とてもではありませんが簡単に理解できるようなものではありません。そのせいもあるでしょうが、一般的には「ポインタ」という言葉の定義は、ないに等しい状態です。p105
ポインタという呼称はややこしいのでやめた方が良い
- 文脈に応じて
- ポインタ変数
- アドレス
- に言い換えること。p106
- 文脈に応じて
配列を引数として受け取る関数の宣言
void func(int *arg)
と書くのが一般的void func(int arg[])
と書くこともできる。- 俺は後者の方が好き。
%p
の出力に関して。- 6Byteのものが表示された。
0x7ffee878ea86
- これは何かのオフセット?
- 一瞬ページングのページオフセットかと思ったけどあっちは12bit
- 物理アドレスではないと思う。
- 6Byteのものが表示された。
ポインタの添字とラベルを変えることができる豆知識の説明
i[5]
は5[i]
と同じ意味- OS自作本ではアセンブリのレベルで説明していた
- この本ではポインタアクセスの中身を加算にして説明している
i[5]
=*(a+5)
=*(5+a)
=5[a]
2次配列はただの配列の配列
- 2次元配列はアドレスで受け取れない
//OK void func1(int array[3][5]){ puts(array); puts(array[1]); } //OK void func2(int array[][5]){ puts(array); puts(array[1]); } //不正 void func3(int *array){ puts(array); puts(array[1]); } void main(){ func1(color); func2(color); func3(color); }
結果
Blue Blue Blue Segmentation fault: 11
うーん最後のセグフォは納得だけど、Grayが渡せていない?
プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み
- 作者: 倉薫
- 出版社/メーカー: 翔泳社
- 発売日: 2009/02/13
- メディア: 大型本
- 購入: 2人 クリック: 6回
- この商品を含むブログ (3件) を見る
C言語の復習を始めました。
こんにちは。大学の1年目のC言語の授業で予約語と非予約語の違いが判らずに 1週間くらい変数には何でもwight,hightを付けていた不届きものです。
最近C言語を再勉強することにしました。
一応大学ではC言語を一番書いていましたが、 C言語そのものの知識というのはあまり深くありません。
ちょっとした事業の数値計算の課題などで使った程度で、 (ポインタなどは積極的に避けていた)
Makeファイルを書いたり、テストを書いたり、 ということはできていませんでした。
C言語は一応読めるけど、複雑なものはわからないし、 大きな規模だったり本格的なモノづくりに活用できるようなスキル ではないということです。
これは今後hariboteOSを改築するのに大きな障害になると思ったのでC言語力を高めたいわけです。
というわけで、もう10年近くプログラミングをやっていますので、 繰り返しはfor文で書きますよ~等は説明されるとくどいような気がするので、
ある程度知っているが深く知らない、人向けの本を探してみました。
ちょうどこんなエントリが目に付いたので早速本を買ってみました。
早速ポインタの節の手前まで終わりました。
本書中の面白いトリビアでも書こうと思います。
charに算術演算子を使って計算することが可能
#include <stdio.h> int main(void){ // Your code here! char a = 3; char b = -5; printf("%d\n",a+b);//-2 a +=253; printf("%d",a);//0 2byte overflow }
こんなことができたなんて。
ポインタにconstをつけると中身の書き換えを防ぐことができる。
//凡例 char* p; このconstはポインタ変数が指している領域を保護 👇 const char* const p; ☝️ こっちのconstはポインタ変数の値を保護
前のconstで参照先をがっちりガード。 後ろのconstで参照そのものを変更させない。
という2重ガードができる。
/** * const char* pで参照先の領域のみを保護する */ void referenceOnlyProtect(const char* p){ p = "test"; //書き換え可能。ただし呼び出し元に影響なし。 //ローカルスコープのvalueというポインタの値のみを書き換えている。 //*p = 's'; //error: read-only variable is not assignable //この変更ができてしまうと呼び出し元の値も変わってしまう。 } /** * char const *pでポインタの値のみを保護する。 * ポインタの指す先が変更できなくなる。 * 参照は書き換え可能 */ void valueOnlyProtect(char* const p){ //p = "test";//error: cannot assign to variable 'p' with const-qualified type 'char *const' //ポインタの値を"test"の参照で書き換えることができない。 //*p = 't';//Bus error: 10 //Busエラーという謎のエラーがでた。 //🚌プップ~ //この書き換えは本書的にはできるっぽいが。。。このエラーは謎。 } /** * 参照先、参照そのものも両方保護する。 */ void valueAndReferenceProtect(const char *const p){ //p="test";//error: cannot assign to variable 'p' with const-qualified type 'const char *const' //参照の変更は許可されない //*p = 't';//error: read-only variable is not assignable //参照先の値も保護されている。 }
概念としては理解できますが、 手前のconstが参照先を保護、奥のconstがポインタのアドレス値を保護という順番が覚えられません。 多分明日には忘れています。 何かいい覚え方はないもんでしょうか。
とりあえず鬼門のポインターまで(p70)進めました。 ここはフレッシュな脳みそで読みたいの今晩はこの辺にしておきます。
30日OS自作本を読み終えた
まぁほとんど著者のコードをなぞっているだけで、 施した改造も少なく、「OSを自作した」なんて口が裂けても言えませんが、 シンプルなコードが動いていく動作を本書や類書の解説とともに追いかけることで 自身の技術力、知見がかなり高まったように思えます。
- 作者: 川合秀実
- 出版社/メーカー: 毎日コミュニケーションズ
- 発売日: 2006/03/01
- メディア: 単行本
- 購入: 36人 クリック: 735回
- この商品を含むブログ (299件) を見る
よかった点
オペレーティングシステムに関連する低レイヤー技術に対する知見が高まる
完全に理解しているわけではありませんが、ざっと思いつくだけで以下のものがあります。
- セグメンテーションとその権限
- VRAM,VESA
- キーボードコントローラ
- BIOS
- アセンブリ言語
- 割り込み
- PIT
- PIC
- CPUの例外
- マルチタスク(タスクスイッチ)
- タイマー
- CPUの動作モード(リアルモード/プロテクトモード)
- IPL,ブートローダー
- メモリ管理の実装
- GDT,LDT
- アプリケーションとOSの違い
こんな記事も書いたのでGDTは割と理解できるんじゃないかな~という気がします。
バイナリに対する苦手意識がなくなる。
- お恥ずかしながら2進数が超苦手。
- 16進数の計算にこちらのサイトに頼り切っていましたが・・・
- 結構紙の上でサクッと計算しようという気になった
- バイナリダンプが割とできる、わかるようになる。
- が、バイナリと触れあう技術・知見がまだまだ弱い
割と世の中のものは自作できるのでは?という気分になってくる
ブルースリーの映画見た後に強くなったと錯覚する現象と同等な気がしますがw
割とこういったものも小さな一歩を積み重ねていけば作れるのでは?という気持ちになってきました。 技術書展でも幾つかこういったものの自作指南本が出ていますね。
不満点
30日でやるのはキツイと思う
- 有能な方が計画的に進めて、脱線しなければ1か月でできるかもしれない。
- たぶん想定読者である初心者にあたるような我々には難しいのでは・・・と思いました。
紙面の都合上難しそうだが、もう少し説明してほしいなという箇所が結構ある。
- 「はじめて読む486」は理解の補助にすごく良い本でした。
32ビットコンピュータをやさしく語る はじめて読む486 (アスキー書籍)
- 作者: 蒲地輝尚
- 出版社/メーカー: KADOKAWA / アスキー・メディアワークス
- 発売日: 2014/10/21
- メディア: Kindle版
- この商品を含むブログを見る
今やっている、これからやり始める初心者に対するアドバイス
とりあえず弱めの人間が終わった時点でのアドバイスを描いて見ます。
著者が用意したツールの代替を利用しないことを推奨
コンパイルやリンクには一部著者の自作ツールを利用します。(z_toolsのnaskや.hrb関係) シンプルなOSをフルスクラッチで・・・という割には一部ブラックボックスなものが絡んでいることに若干の不満があるかもしれません。
Linuxなどで一般的なツールであるnasmやelfを使いたくなるかもしれませんが、 もちろんそれらは著者のツールとは挙動が違うので、調査してツールのオプションを工夫したり、 あるいはOSそのものに変更を入れる必要があるかもしれません。
ある程度知識がある人なら時間をかけてトラブルシュートできると思うが、 全く知識がない場合はただ地図を持たずに道に迷うようなもので ネットを検索しまくれば、
- この行を消すと動くようになる
- この値をXXに変えるとコンパイルできる
などの断片的な情報を参考にいろいろいじくりまくった結果、 たまたま動くようになるかもしれませんが、労力の割に得られるものが少ないと思います。
などの凄い方もおられますが、すでに何冊か難しそうな本を読破されていたり、その道のプロの方だったりするので、 難しすぎることに挑戦して挫折するより、初学者は素直に著者のツールを使い、この本の枠組みで進めるのが良いと思います。 実力に自負があったり、力試しがしたい人はこの限りではありません。頑張ってください。
解説ページがコメントで張られているヘッダファイルを利用する
この本は700ページ以上もあるのに牽引があまり充実していません。 また、初日の方に解説されている関数の内容を忘れることもあると思います。
つまり、どこに何が書かれているか、というのが結構わからなくなります。
そこで付録には最終日のヘッダファイルの関数や定数の宣言箇所に、 そこを解説しているページをコメントで補足しているものがあります。
印刷しておくと便利。改造の流れも忘れたとき把握できる。30日立った後に気づいて泣いた http://d.hatena.ne.jp/sandai/20120728/p2
この本の感想を読んでると、このヘッダファイルにもっと早く気付けばよかった というものも少なくはありませんが、これの存在は早いうちから知っておいて損はないでしょう
今後
- C言語の理解(ここがガタガタなので)
- 基礎(おもにポインタまわり)
- 応用(開発ツール、開発手法、テスト等)
- hariboteOSにないOSとして重要な機構(電源管理、ページング、外部記憶装置、ネットワークドライバ等)
- 特定のCPUやそのエミュレータの学習
- OSSになっているOS(Linux等)の調査や理解を深める
- CTF
その道のプロの方に勧められたので
ゴメンなさい認識不足でしたが、もう28日目でしたか!!!強い…!!!
— 堕天使ヴァネロピ (@Vane11ope) 2018年6月29日
低レイヤとかそもそもバイナリとかお好きなようであればそのあとCTFとかに進まれても面白いかもしれません…!ブログなど見ましたが、この方もOS自作本から始められていますね。(凄いタイトルだ..)
しばらくLinuxやUnix(xv6)、モダンなCPUの勉強をしたいと思います。(Linuxの仕組みが9割程度読了しています。) hariboteOSに機能を足すなら、電源管理と外部記憶装置とページングを実装したい。
最近のKindleセールで買った3冊
コンピュータネットワーク第五版
1年くらい前からほしい物リストに入れていましたがなかなか半額になりませんでした 今回は半額で買えた8640 -> 4320円
- 作者: アンドリュー・S・タネンバウム,デイビッド・J・ウエザロール,水野忠則,相田仁,東野輝夫,太田賢,西垣正勝,渡辺尚
- 出版社/メーカー: 日経BP社
- 発売日: 2013/09/12
- メディア: 単行本
- この商品を含むブログ (4件) を見る
コンピュータの構成と設計第5版 上下電子合本版
所謂ヘネパタ。
紙で上下揃えると9千円以上するが、3780円で買えた
- 作者: デイビッド・A・パターソン
- 出版社/メーカー: 日経BP社
- 発売日: 2016/10/26
- メディア: Kindle版
- この商品を含むブログ (5件) を見る
APIデザインの極意
ほぼ半額。3456 -> 1620円
APIデザインの極意 Java/NetBeansアーキテクト探究ノート
- 作者: Jaroslav Tulach
- 出版社/メーカー: インプレス
- 発売日: 2014/07/02
- メディア: Kindle版
- この商品を含むブログ (8件) を見る
余談
どれも結構読むのに時間がかかりそう ツイッター控えたり、隙間時間を有効活用する必要がありそう。