見出し画像

(答案提出)C言語教室 第7回 - 動的なメモリ割り当て

私はkznさんのC言語教室の記事が毎週とても楽しみ!
永らくご無沙汰していた、プログラミングの記憶が蘇ってきます。

今回の答案は、、、

正直言って、あまりしっくりきません。
先ずは、課題を見ていきましょう。

課題

引き数で渡された文字列を、関数の中で動的に確保したメモリに文字列をコピーし、(引き数で渡した方ではなくて)コピーした文字列の先頭を指すポインタを返す関数を書いてください。

https://note.com/kazushinakamura/n/ne2c4e0ba4bee

前回の課題(第6回)のように、呼ぶ側で用意した配列に、呼ばれた関数で処理した結果をセットするというのであれば、設計し易いです。呼ぶ側と呼ばれる側の機能を綺麗に分けることができますからね。

しかし、「引き数で渡された文字列を、関数の中で動的に確保したメモリに文字列をコピー」ということであれば、呼ばれた方の関数の中で動的配列を確保することを求められているようですし、「コピーした文字列の先頭を指すポインタを返す」となりますと、その動的配列を呼び出した側で使うということのようです。動的配列は使用後には開放しなければなりません。ということは、呼ぶ側で、呼ばれる側の内部処理(動的配列を作成したこと)を知っておかなければなりません。これだと関数の独立性を確保できず、美しくありません。

私としては、関数は一つの目的のために括られたコードで完結するのが美しいものだと考えていまして、その関数内で動的に確保された使用済みメモリは当然に、その関数内で開放すべきでは無いかと考えています。

きっとこれは罠だ、何かひっかけがあるはずだ、、、。

と、悩んだ挙句、

とNOTEで弱音を呟いたら、

と、先生からコメントをいただきました。
ひょっとしたら、、、同じ件を言われているのかもしれません。
先ずは、先生の回答発表を待ちましょう。


私の答案 - 動的に作成した配列に文字列をコピーする関数

その後も、私の限られた知恵を絞ってみましたが、何ら妙案が出てきませんので、素直にコーディングした結果を答案として提出します。

これを叩き台にして、話が進むといいなと思ってます。

/* main.c */ 
#include <stdio.h>
#include <stdlib.h>

char * copyStr(char* x) {
  char *d;
  char *p, *q;
  int i = 0;

  for (p = x; *p != '\0'; p++)
    i++;
    
  d = (char*)malloc(i);

  p = d;
  q = x;
  while ((*p++ = *q++) != '\0');

  return d;

}

void main() {
  char src[] = "abcdefghijklmn";
  char *dup;

  dup = copyStr(src);

  printf("source =      %s\n", src);
  printf("duplication = %s\n", dup);

  free(dup);
}

main関数最後の free(dup); が何処からともなく、いきなり出てきた感じがとても気に障ります。


実行結果 - 動的に作成した配列に文字列をコピーする関数

source =      abcdefghijklmn
duplication = abcdefghijklmn

どうでしょうか? 
一応は、機能しているようです。
(動作環境;https://9cguide.appspot.com/web_picoc_em.html )

ちなみに、少し乱暴ですが、呼ばれた側の関数内でメモリを開放しても、結果は同じになりました。これは偶然なのか、実質的にそう言うものなのか?
よくわかりません。

また、動的配列のサイズを文字数で指定する時、char 型の大きさは1バイトであることはC言語で共通とのことなので、数えた長さをそのまま渡していますが、日本語を元データとして渡してみてもきちんとコピーしてくれました。漢字一文字が3バイトのようです。

文字列の長さ  = 9
source      = 日本語
duplication = 日本語

(2022.12.17追記;漢字一文字が何バイトであるかは、使用する文字コードに依存する旨、ご指摘を受けました。kznさん有難うございました。)


答え合わせが楽しみです!

*この記事は、私こと、Akio van der Meerが、kznさんの記事にインスパイアされて、私の責任の下で作成したものです。


(独り言)

悪天候で長時間足止めをさせられた空港ロビーで、iPad Air2を使ってデバッグしてました。眠くなった時の気分転換に丁度良いです。でも、画面上の分割キーボードでプログラムコードを編集するのは、ちょっとしんどいです(笑


追記(2022.12.17)

今回も、kznさんから答案に対するコメントをいただきました。
kznさん、ご指導有難うございました。

なるほど、、、、、、、、、、、、、、、、

kznさんの解説を拝読して、私の上述の感想がいかに稚拙であったかを思い知らされた気分です。私自身、かつて一度もC言語の世界を見渡せた経験がないため、あたかも「英語ってさぁ、主語の次に動詞がいきなり来るのって、奥ゆかしさがないよねぇ」みたいなレベルの話をしていたようです。
お恥ずかしい限り。

さらに、kznさんの回答や、AyumiKatayamaさんの回答を拝見すると、私の懸念も解消しました。要は、いかにメンテナンス性のいいコードを記述できるかについては、当たり前ですが、いくらでも解決法があると言うことでした。こういうところが、文法書には出てこない技なんでしょうね。

こちらは、AyumiKatayamaさんのアドバイスに従って修正したバージョン。
可読性が上がりました。有難うございます!

ついでに、私の答案では動的配列のサイズの設定が誤っていて、文字列最後の'\0'を格納する部分が考慮できていなかったので、こっそり修正しておきます(笑

/* main.c */ 
#include <stdio.h>
#include <stdlib.h>

char * copyStr(char* x) {
  char *d;
  char *src, *dst;
  int len = 0;

  for (src = x; *src != '\0'; src++)
    len++;
    
  d = (char*)malloc(len + 1);

  src = x;
  dst = d;
  while ((*dst++ = *src++) != '\0');

  return d;

}

void main() {
  char src[] = "abcdefghijklmn";
  char *dup;

  dup = copyStr(src);

  printf("source =      %s\n", src);
  printf("duplication = %s\n", dup);

  free(dup);
}

いゃー本当に面白い。
実務経験の無かった言語を改めて学ぶことを通して、新しい「ものの見方」が広がるようです。

ここまで読んでいただき、有難うございました。

これまでの収益は全て、それを必要としておられる方々へ、支援機関を通して寄付させていただきました。この活動は今後も継続したいと思っています。引き続きよろしくお願いいたします。