Yabu.log

ITなどの雑記

C言語の復習その4。enum,3項演算子,goto,関数

関数ポインタをちょっと読み込んだくらい進みましたが、 関数ポインタは難しそうなので、それで一つ記事を書きます。

というわけでキリが良いので投稿♪

enum

  • enumのメンバは本来値を持たない。(持つべきではない)
  • 実は数値型の値を持っている。
    • 先頭から0,1,2,3...と割り振られている。
typedef enum
{
  SUCCESS, //0
  FAILURE  //1
}Result;
  • ただしこの値を直接計算・表示に使うのはenumの利用方法としてはふさわしくない。
    • K&Rにこの「ふさわしくない利用例」が乗ってしまっているらしい。
  • 確かJavaenumも内部では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)さんからこんな指摘をいただきました。

Javaと違いC言語のemumは単なるint型です。 C言語は弱い型付けなので普通にintのように扱えます。

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のラベルは関数ブロックの最後尾にかけない?

    • 手元のGCCでは上記のGOTOを使った関数は最終行のprintf("\n");を抜くとエラーになった。
      • またまた某所で教えてもらいましたが、

      ラベルの次行に空文であるセミコロン ; を置けば、ブロックの最後尾にラベルを書くことが出来ます。

      • とのことです。検証したところ確かにコンパイル&実行できました。
  • 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はだいたい上記の認識でいいが変数は別。

fa11enprince.hatenablog.com

C言語能力が着実にレベルアップしています。かなり良い本だと思います。

プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み

プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み

プログラマのための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://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%83%E3%82%AB%E3%83%BC%E3%83%9E%E3%83%B3%E9%96%A2%E6%95%B0

だそうです。

郵便番号はユニークではない。

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この業界意外と狭い!

プログラマのためのSQL 第4版

プログラマのためのSQL 第4版

*1:急に出てきたサブクエリが中途半端だな...と読んでて思ったのです。

C言語復習その3構造体、共用体について

構造体と共用体について学びました。

構造体のサイズについて

  • コンパイラが勝手にサイズを偶数長になるように変更する
  • 構造体の正確なサイズはわからない。
    • sizeof演算子で大きさを求めるべき。
      • 実際の現場でもsizeof演算子で大きさを求める場面は結構あるらしい。

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",&registers.ah);
//bitフィールドを使った構造体のアドレスは取得可能。
printf("registers adress is %p\n",&registers);

共用体

  • ある値の何バイト目まで、というような値の参照ができる。
  • 見た瞬間にCPUのレジスタを扱うのに向いているのでは?と思った。
  • hikaliumさんが過去に作られていたOSで適当にunionとググってみたところcpuのレジスタのbitを共用体で表現しているところがありました。

github.com

  • EFLAGSやCRnレジスタはnビット目がこれ!という表現を共用体を使わないと混乱すると思います。
  • Linuxでも共用体でレジスタっぽいものを表現していました。*1
  • たぶんCPUのレジスタを共用体で表現するということは普通に行われていることなのでしょう。
    • 本書ではほとんど使われない、と書かれているが、ある程度低レイヤーの分野の書籍を読んだことがあると用意の活用イメージが湧く。

確かに面白いのですが、実際のところ普通のプログラミングをしている限り、ほとんど用途がありません。ですから、こういうものがある、ということだけ覚えておけば十分でしょう。

  • COBOLに似たような機能があった気がする。レベル変数?とか言った気が。

tallercolibri.com

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の仕組み

プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み

*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
  • 大きな配列をローカル変数として宣言するとスタックを使い過ぎてしまう。

    • コンパイラは特にこの件に関して警告を出さない
    • 対策
      • コンパイル時にスタックの領域を大きくする
      • staticやグローバル変数を使うようにしてスタックを使わなくする
      • mallocを使い明示的にメモリを確保する
      • メモリ上の変数を使わない(ファイルで扱う)
  • C言語では関数には値そのものではなく、値のコピーが渡るらしい。

    • アセンブラ(objdump)で見て見たけど、よくわからなんかった
    • 配列を渡すと、配列のアドレス(のコピー)が渡る
      • 配列のコピーを渡す方法はない。
  • ポインタの正確な定義は難解らしい

    ポインタという言葉の定義は、実はあんまりはっきりとは決まってないらしい 注2-A正確にいうとANSIの仕様として正確に決まってはいます。が、それを定めた文章はあまりに難解で、とてもではありませんが簡単に理解できるようなものではありません。そのせいもあるでしょうが、一般的には「ポインタ」という言葉の定義は、ないに等しい状態です。p105

  • ポインタという呼称はややこしいのでやめた方が良い

    • 文脈に応じて
      • ポインタ変数
      • アドレス
    • に言い換えること。p106
  • 配列を引数として受け取る関数の宣言

    • void func(int *arg)と書くのが一般的
    • void func(int arg[])と書くこともできる。
    • 俺は後者の方が好き。
  • %pの出力に関して。

    • 6Byteのものが表示された。
      • 0x7ffee878ea86
    • これは何かのオフセット?
    • 一瞬ページングのページオフセットかと思ったけどあっちは12bit
    • 物理アドレスではないと思う。
  • ポインタの添字とラベルを変えることができる豆知識の説明

    • i[5]5[i]と同じ意味
    • OS自作本ではアセンブリのレベルで説明していた
    • この本ではポインタアクセスの中身を加算にして説明している
      • i[5]=*(a+5)=*(5+a)=5[a]
  • 2次配列はただの配列の配列

  • 2次元配列はアドレスで受け取れない
    • void func(int arg[])=void func(int *arg)
    • void func(int arg[3][5]) = void func(int arg[][5])
      • void func(int *arg)これで多次元配列は受け取れない
      • 配列の関数が受け取るときには、要素数は最初の要素数だけが省略できる(p117より)

    • なぜか本書の内容を検証したがうまくいかなかった。
//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の仕組み

プログラミング学習シリーズ C言語改訂版 2 はじめて学ぶCの仕組み

C言語の復習を始めました。

こんにちは。大学の1年目のC言語の授業で予約語と非予約語の違いが判らずに 1週間くらい変数には何でもwight,hightを付けていた不届きものです。

最近C言語を再勉強することにしました。

一応大学ではC言語を一番書いていましたが、 C言語そのものの知識というのはあまり深くありません。

ちょっとした事業の数値計算の課題などで使った程度で、 (ポインタなどは積極的に避けていた)

Makeファイルを書いたり、テストを書いたり、 ということはできていませんでした。

C言語は一応読めるけど、複雑なものはわからないし、 大きな規模だったり本格的なモノづくりに活用できるようなスキル ではないということです。

これは今後hariboteOSを改築するのに大きな障害になると思ったのでC言語力を高めたいわけです。

というわけで、もう10年近くプログラミングをやっていますので、 繰り返しはfor文で書きますよ~等は説明されるとくどいような気がするので、

ある程度知っているが深く知らない、人向けの本を探してみました。

ちょうどこんなエントリが目に付いたので早速本を買ってみました。

hatomu555.hatenablog.com

早速ポインタの節の手前まで終わりました。

github.com

本書中の面白いトリビアでも書こうと思います。

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を自作した」なんて口が裂けても言えませんが、 シンプルなコードが動いていく動作を本書や類書の解説とともに追いかけることで 自身の技術力、知見がかなり高まったように思えます。

30日でできる! OS自作入門

30日でできる! OS自作入門

よかった点

オペレーティングシステムに関連する低レイヤー技術に対する知見が高まる

完全に理解しているわけではありませんが、ざっと思いつくだけで以下のものがあります。

  • セグメンテーションとその権限
  • VRAM,VESA
  • キーボードコントローラ
  • BIOS
  • アセンブリ言語
  • 割り込み
    • PIT
    • PIC
  • CPUの例外
  • マルチタスク(タスクスイッチ)
  • タイマー
  • CPUの動作モード(リアルモード/プロテクトモード)
  • IPL,ブートローダ
  • メモリ管理の実装
  • GDT,LDT
  • アプリケーションとOSの違い

こんな記事も書いたのでGDTは割と理解できるんじゃないかな~という気がします。

バイナリに対する苦手意識がなくなる。

  • お恥ずかしながら2進数が超苦手。
    • 16進数の計算にこちらのサイトに頼り切っていましたが・・・
    • 結構紙の上でサクッと計算しようという気になった
  • バイナリダンプが割とできる、わかるようになる。
    • が、バイナリと触れあう技術・知見がまだまだ弱い

割と世の中のものは自作できるのでは?という気分になってくる

ブルースリーの映画見た後に強くなったと錯覚する現象と同等な気がしますがw

割とこういったものも小さな一歩を積み重ねていけば作れるのでは?という気持ちになってきました。 技術書展でも幾つかこういったものの自作指南本が出ていますね。

不満点

  • 30日でやるのはキツイと思う

    • 有能な方が計画的に進めて、脱線しなければ1か月でできるかもしれない。
    • たぶん想定読者である初心者にあたるような我々には難しいのでは・・・と思いました。
  • 紙面の都合上難しそうだが、もう少し説明してほしいなという箇所が結構ある。

    • 「はじめて読む486」は理解の補助にすごく良い本でした。

今やっている、これからやり始める初心者に対するアドバイス

とりあえず弱めの人間が終わった時点でのアドバイスを描いて見ます。

著者が用意したツールの代替を利用しないことを推奨

コンパイルやリンクには一部著者の自作ツールを利用します。(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
    • その道のプロの方に勧められたので

    • ブログなど見ましたが、この方もOS自作本から始められていますね。(凄いタイトルだ..)

vane11ope.hatenablog.com

しばらくLinuxUnix(xv6)、モダンなCPUの勉強をしたいと思います。(Linuxの仕組みが9割程度読了しています。) hariboteOSに機能を足すなら、電源管理と外部記憶装置とページングを実装したい。

*1:まだC言語があやふやな段階だが

最近のKindleセールで買った3冊

コンピュータネットワーク第五版

1年くらい前からほしい物リストに入れていましたがなかなか半額になりませんでした 今回は半額で買えた8640 -> 4320円

コンピュータネットワーク 第5版

コンピュータネットワーク 第5版

  • 作者: アンドリュー・S・タネンバウム,デイビッド・J・ウエザロール,水野忠則,相田仁,東野輝夫,太田賢,西垣正勝,渡辺尚
  • 出版社/メーカー: 日経BP
  • 発売日: 2013/09/12
  • メディア: 単行本
  • この商品を含むブログ (4件) を見る

コンピュータの構成と設計第5版 上下電子合本版

所謂ヘネパタ。

紙で上下揃えると9千円以上するが、3780円で買えた

コンピュータの構成と設計 第5版 上・下電子合本版

コンピュータの構成と設計 第5版 上・下電子合本版

APIデザインの極意

ほぼ半額。3456 -> 1620円

余談

どれも結構読むのに時間がかかりそう ツイッター控えたり、隙間時間を有効活用する必要がありそう。