const char*

  • const char* の意味

C言語編 - char *とconst char *は違う
to make sure you don’t alter the array of characters (not the pointer for it, it is changable) stored in const char*.
「const char *」というデータ型は、書き換えできない(読み取り専用の)文字列に対して使います

おまけ(※wordはconst char*型)

ゆに「sizeof(word)ってword変数の型のサイズになっちゃうんだよね。だからこの場合、sizeof(word)は常にsizeof(const char*)になっちゃう。const char*を入れるために必要なサイズですね〜今回の場合、ほしいサイズはstrlen(word)+1かなあ」
私「えっと、ちょっとずれるけど、const char*を入れるために必要なサイズっていくつかわからなくない?stringの長さって謎じゃん」
ゆ「や、アドレスそのものを入れるってことだよ〜文字列の中身ではなく、文字列の先頭のアドレスね」
私「住所を入れるはこってことか。
この場合のconstは箱の中身(アドレス)にかかってるのではなく、そのアドレスの中身(文字)なんだよね...複雑すぎる。箱の中身の中身」

ゆ「そうそう。うむ、めっちゃややこしい…」
私「で、malloc()は()内にバイトを指定するのだけれど、幸運にもchar1文字あたり1バイトなため必要なバイト数はwordの文字数+1(\0)となるわけですね。」
ゆ「そうそう!まさにそうです!」

**


私「if (lowerword == NULL) return word;
でいいかな52行目は。なんとしてでもchar*型で返さなきゃならないので、無理やりchar*型の答えを返さなきゃならないのよね。
(今回だったらlower lettersにしないでそのまま返してしまう、とか)」

ゆ「そういうときはNULLを返せばよいよ〜」
私「えーっ!そうなの?!あ、ポインタだから…?」
ゆ「うん!」
私「なんだかある時はstringとみなして、ある時はポインタとみなしてって忙しいなchar*…」
ゆ「まあねw 実際、2つの側面をうまいこと使い分けてプログラミングしないとすごい大変です…w」

Pset5 - tips

In speller.c, we’ve put together a program that’s designed to spell-check a file after loading a dictionary of words from disk into memory.

"free関数を使い忘れてもプログラム終了時には自動的に解放されますが、その間不必要にメモリを占有していることになるわけですから、用が済んだら速やかにfreeで解放するのが原則です。"

  • c言語でstring (char array)を作る時、最後に\0は入れましょう。
  • 種々の関数やhash table等、定義に関してはmainから外れていい。だが、実行して欲しい時はmainに入れ、自分で呼ぶべし。

構造体の宣言では、箱の形を決めているだけなので、構造体の中で値は指定できない。

typedef struct node
{
    char word[LENGTH + 1];
    struct node* next = NULL;
}
node;

とか、だめで、

typedef struct node
{
    char word[LENGTH + 1];
    struct node* next;
}
node;

と言ってから
node* new_node;
new_node->next = NULL;
が正しい。う〜ん、面倒...

  • valgrind とmakeとで実行結果が違うの...メモリまわりがバグってるとよくあるのね(唖然)

f:id:owan_k:20160917225420p:plain
f:id:owan_k:20160917225429p:plain
For counts of detected and suppressed errors, rerun with: -vは、
valgrind -v speller ...と実行すれば良い。以下も参考にしつつ。
Valgrindの結果の見方、日本語訳、など役に立つことまとめ - 結果だけでなく過程も見てください
色々調べていると...おんなじエラーのひとをみつけた!
cs50.stackexchange.com
修正後:
f:id:owan_k:20160917225919p:plain
lower word最後の'\0'を忘れていたのでした。元々のstringを完コピするなら最後も勝手に0が入るんでない?と考えていたものの、冷静になってみると<の場合ゼロだけコピーされないね。

for (int k = 0; k <= strlen(word); k++) {
       lowerword[k] = tolower(word[k]);
   }

にしてもよかったかな。
ちなみに、結局cs50.stackexchange.comに影響されて書き換えたけれど、LENGTH使う必要もなかったかな。strlen(word) + 1でも事足りたと思われる。

scanf

scanf使い方
C言語 scanf 使い方 | C言語関数一覧~bituse~

scanfのバッファーオーバーフローネタ
http://d.hatena.ne.jp/eel3/20090720/1248017333
対策として%の後ろに最大フィールド長と呼ばれる数字をつけて「%10s」と指定すると、
10文字だけ読み込むという指定ができます。
最大フィールドは場を指定すれば、入力最大文字数を制御できますね

おまけ
f:id:owan_k:20160917223447p:plain
この時ぐぐったもの
error: array type 'char [46]' is not assignable
https:www.reddit.com/r/cs50/comments/1xkmgi/pset6_array_not_assignable/
https:www.reddit.com/r/cs50/comments/2bhioh/pset6_load_function_problem/
(特に下、わかりやすい)

Pset5- Why use fgetc instead of fscanf?

questions.txtの5問目。

cs50.stackexchange.com
“words in that file will not always end with a space or line return. Many will be followed by a period or comma, for example, which fscanf will see as being part of the word.”

“fgetc() reads character-by-character from a stream. This will enable us to keep track of the number of chars we read, while using fscanf(), on the other hand, might get us into trouble if a longer string than we expect was read.
This might overwrite other important data in memory or probably makes our program exist with a segmentation fault.”

おまけ
detail.chiebukuro.yahoo.co.jp

Problem Set 5 - debugリスト

やっつけ仕事の元のコード
dictionary.c · GitHub
dictionary.h · GitHub
(おまけにコンパイル前でたくさんエラーあり。ざっと目を通しただけでも直すべき箇所を以下に)

  • #include ”dictionary.h”をどこに入れるか
  • hash関数をdictionary.cで定義してもOK、global 関数として定義すればOK
  • char配列をwhileの外で宣言する、空のテンプレートを作っておき、whileの中でnodeを宣言する。

→コピーする
== EOFを使わない理由はメモリリークをしないようにするため。
== EOFを使用する場合、mallocでallocateしたmemoryがlinked list及びhash tableに入らない→freeでfreedすることができない。→memory leakを防げない
(左辺 != EOFはしばしば用いられるが、左辺 == EOFは滅多に見ないという直感通りで。理由があったのね)

copyするやり方だとmemory leakしないで済む。

- whileが終わった直後にreturn true;を書く。
(全て成功裏に終わったときに最後に一回だけ記す)
return false;は、失敗する場所がいくつかあるので、各々の箇所にちょこちょこreturn false;を書いていく。mallocできない時とか。
すなわち、きちっとfalseのcheckをしていないと、大したチェックも成されないままtrueが返され得る。

- mainの返す値は0がtrueと見なされるが、c言語全体では0がfalse!!

  • countはdictionary.cで宣言すべき。or headerに置いたままにするならstaticをつけること。

(なぜならリンクした時に重複してしまい、エラーになる)

  • count部分、return countしてるのと一緒
  • unload falseも適当にfalse地点を定めておく。(どういう場合に失敗するかぐぐる)

freeは簡単にエラーを返さないのでreturn false;なしもアリかもしれない。

Problem Set 5 - Makefile

Makefileのメモ、箇条書き

speller: コロンの前は新しくできるファイルの名前
speller.oとdictionary.oを基にして新しいファイルができる。
上の2つこいつらに使われるのがdictionary.h

$@は:の前に書いてあるものに展開される(=置換される)=spellerのこと

make speller の内訳は以下のよう---

speller: speller.o dictionary.o
clang -o speller speller.o dictionary.o

speller.o: speller.c dictionary.h
clang -o speller.o speller.c

dictionary.o: dictionary.c dictionary.h
clang -o dictionary.o dictionary.c

ここまで---

LIBSは今回は関係無い(だから省略した、結局空白に展開されるから)

Pset5に合わせたコンパイラ考察

hash関数のhash_itをdictionary.hに定義したとする。複数の.c fileでheader file (今回は、dictionary.h)をincludeすると...
コンパイラはhash_itを2つ生成してしまう。(☆)

その理由を詳しくみてみる。コンパイルという過程は以下の4つからなる:
1) プリプロセッサがまず走る (= プリプロセッシング)
#defineが目的の単語を置換したり、#includeが展開など諸々の処理がなされる。
includeの中身はワッと.c fileの一番上に乗ってくる。
(= dictionary.cとspeller.c両方でinclude内のdictionary.hが展開される)

2) compilation (= コンパイル)
.c fileを.oにする
3) linking (= リンク)
複数の.oを繋ぐ。
.cが複数あると
プリプロセッサコンパイル→.oになってからリンクされ走る。
.oになった段階では1つのファイルに関数が足りていなくても大丈夫。
例えば、speller.cとdictionary.cがあるとき
speller.oとdictionary.oができ、
この2つの結合をmakeが行う。
もっと言うと...speller.oはload関数を持っていないが、speller.oがload関数を欲しがっているという情報は存在する。(実行する為にはこの関数が必要です、という一覧は入っている)

3)linkは「コンパイル後、.oを寄せ集め、足りない関数を補い合わせ(他の.oから)」、4)プログラムとして動かす。
4) プログラムが走る

(☆)に戻ると、
2つの.oは独立に作られている→hash_itが2つ出来てしまい、リンクの時に失敗する。

解決策は...

  • static 関数を利用する

static を接頭語とし関数を宣言すると、そのファイル中だけで使える関数となる。感覚としてはlocal 変数と同じ。ファイルローカル関数と言っても良い。同じ内容だが各々のファイルにおけるローカル関数として扱われる。これなら、リンクの時に他の.oに同じ関数名があっても大丈夫。
今回のケースと同様の理由で、ヘッダに関数を定義するときはstaticを付けるという慣習がある。
欠点は、無駄にメモリを食うこと。
(ファイルサイズはあまり速度に関係無いため、今回の課題ではstatic関数として定義し、重複しても問題ないだろうけど)

  • hash_itをdictionary.cで定義する

dictionary.cの上方(load, check, size, unloadの外)にglobal 関数として定義すればOK。
link後は、dictionary.c・speller.cの2つがもともと何のファイルだったか関係なく完全に融合している・溶け込むためdictionary.cに1度のみ定義していればよい。
speller.cでloadを使いたいときは...
①speller.cでload関数を宣言していないと、load関数の型がわからない→エラー
(宣言しなかったらloadがそもそも関数なのかどうかコンパイラが知らない、そんな名前知らないよふええとなってしまう)
②宣言さえしていればloadは関数だということをコンパイラは知っている→その中身がどうなっているか知らなくてもコンパイル自体は出来て、リンクするときに別ファイルにでもloadの中身が含まれていればOK