質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

5回答

4025閲覧

任意の個数の任意の長さの文字列の領域を動的に確保、参照する方法は?

omologic

総合スコア6

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2020/04/19 13:28

前提・実現したいこと

任意の個数の任意の長さの文字列の領域を動的に確保したい、参照したい。

以下の問題をC言語で解くにあたり、障害が発生したのでお力添えください。
https://atcoder.jp/contests/abc042/tasks/abc042_b

辞書順に文字列を連結するというシンプルな問題なのですが、

文字列について、
最初に任意の個数の任意の長さをユーザーに入力させるという仕様があります。

制約に従って当初から静的に文字列の個数や長さを定義することもできるのですが、
理解を深めるために動的に領域を確保することにチャレンジしたいと思います。

mallocをつかった場合 文字列の個数、文字列+ヌル文字分の1で必要な領域は確保できると思うのですが、
その呼び出し方法(S[i])が間違っているのか正常にprintされません。

どこが間違っているのかご指摘いただけますでしょうか?

該当のソースコード

C言語

1#include <stdio.h> 2#include <stdlib.h> 3#include <string.h> 4 5int main(void){ 6 int N,L; 7 char *S; 8 scanf("%i %i", &N,&L); 9 10 S = malloc(sizeof(char)*N*(L+1)); //**任意の個数の任意の長さの文字列の領域を動的に確保する方法はこれで正しいのか?** 11 for (int i=0; i<N; i++){ 12 scanf("%*s",N,S[i]); 13 } 14 //qsort(S,N,sizeof(*S),(int(*)(const void *, const void *))strcmp);  まだ未検証の部分です 15 16 17 //以下のループが正常に実行されていないようです(各インデックスの文字列の内容がprintされない) なにが間違っているのでしょうか? 18 for(int i=0; i<N; i++){ 19 printf("%s",S[i]); 20 } 21 22 23 free(S); 24 25 return 0; 26 27}

補足情報

42tokyoの試験が控えており、C言語で学習しています。(本当は3月試験の予定でしたが、コロナの影響で・・・)

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

ベストアンサー

S[i]*(S+i)と等価です。
つまり先頭からi番目の要素となっており、意図したアドレスへのアクセスになっていません。
&S[i * (L + 1)]ですね。

scanf("%*s",N,S[i]);
書式指定%*sは読み取ったものを格納しません。
引数も誤っています。

あなたがやりたいことは
scanf("%s", &S[i * (L + 1)]);
printf("%s", &S[i * (L + 1)]);でしょう。

(追記)
printの文字数を指定する書式指定%*sとは異なり、scanfの書式指定%*sは読み捨てを意味しています。
L文字制限をかけたいならこんな感じですね。

C

1 char format[15]; 2 sprintf(format, "%%%ds%%*[^\n]%%*c", L); 3 for (int i = 0; i < N; i++) { 4 scanf(format, &S[i * (L + 1)]); 5 }

%*[^\n]でL文字を超えた部分の改行文字以外を読み捨て。
%*cで改行文字を読み捨て。

投稿2020/04/19 13:51

編集2020/04/19 16:41
SHOMI

総合スコア4079

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

omologic

2020/04/19 14:15

詳細に解説いただきありがとうございました。 はじめ、まったく意味がわかりませんでしたが、 なるほど、char型のポインタはインデックス1増加につき、1つのchar分しか進まないのですね。 またprintについても &がいる件について、指摘されてはじめて気づきました。 文字列はアドレスを渡さないと%sで表示されないんでしたね・・・
guest

0

char* の変数 S から S[i] で出てくるのは当然 char です.
printf の "%s" はアドレスを要求しますから, 正常には実行できないでしょう.

考え方としまして, (malloc は成功していると思いますので) malloc の個所で char S[N*(L+1)]; と書いてある"つもり"でその後の処理を書くと良いのではと思います.

投稿2020/04/19 13:49

編集2020/04/19 14:00
jimbe

総合スコア13209

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

omologic

2020/04/19 14:19

ご回答ありがとうございました。 おかげ様で、char型のポインタ変数に対して、インデックスを使っても、 勝手に文字列に合わせて進んでくれるものではないということを理解できました。
guest

0

領域を確保するのはmalloc関数でできます
確保した領域を開放するのはfree関数です

ここらへんのキーワードでぐぐれば解説が出てきますね

んで、

printf("%s",S[i]);

これが間違っています
フォーマット文字列%sは文字列を指定します。が、char型を引数にしてますんで、でたらめな出力しますね

投稿2020/04/19 13:43

編集2020/04/19 13:47
y_waiwai

総合スコア88042

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

omologic

2020/04/19 13:49

ご回答ありがとうございます。 はい、ご指摘のとおりmallocを使うだろうというところは予想できたのですが(掲載ソースコードのとおり)、 インデックスでうまく文字列を表示することができず、なにが間違っているのか見当がついていない状況です。自分なりにググってみましたが、適切な情報を見つけることができませんでした。
y_waiwai

2020/04/19 13:51

回答に追記しています
omologic

2020/04/19 14:11 編集

文字列の取り扱いがよく分からなかったんですが、char型のポインタだとインデックス1増加につき、あくまで1バイトずつしかすすまないってことなんですね。 あいまいな理解だったのですがようやく理解できました。ありがとうございます。
y_waiwai

2020/04/19 14:15

1.Sに領域確保したアドレスを代入してるが、そのSの中身はなにも設定していない。   Sの中身はデタラメの内容となる 2.Sに入っている文字列を出力するなら、 printf("%s",S); とするべし。 しかし、1.の指摘のように、Sの内容は初期化されてないので、これでもデタラメの内容が出力される
guest

0

文字長チェックとかばっさり省略

C

1#include <stdio.h> 2#include <string.h> 3#include <stdlib.h> 4 5int comp(const void* px, const void* py) { 6 const char* x = *(const char**)px; 7 const char* y = *(const char**)py; 8 return strcmp(x,y); 9} 10 11int main(void){ 12 char** table; 13 char* result; 14 int N; 15 int L; 16 scanf("%d", &N); 17 scanf("%d", &L); 18 19 table = (char**)malloc(sizeof(char*)*N); 20 int i; 21 for ( i = 0; i < N; ++i ) { 22 table[i] = (char*)malloc(L+1); 23 scanf("%s", table[i]); 24 } 25 qsort(table, N, sizeof(char*), comp); 26 result = (char*)malloc(N*L+1); 27 *result = '\0'; 28 for ( i = 0; i < N; ++i ) { 29 strcat(result, table[i]); 30 free(table[i]); 31 } 32 free(table); 33 puts(result); 34 free(result); 35 return 0; 36}

投稿2020/04/20 00:57

編集2020/04/20 03:40
episteme

総合スコア16612

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

omologic

2020/04/20 06:12

qsortの実装までご教示いただきまして、ありがとうございます。 丁度つまづいていたところなので、大変助かりました。 見当違いの質問かもしれませんが、ソート対象がchar型のポインターのポインターかと思うのですが、qsortで使う要素サイズの指定に sizeof(char**) ではなく、sizeof(char*)を使っているのは、どのみちchar型のポインターもポインターのポインターも同じサイズだから、ということでしょうか?
episteme

2020/04/20 11:10

> qsortで使う要素サイズの指定に sizeof(char**) ではなく、sizeof(char*)を使っているのは 要素の型が char* なので要素サイズは sizeof(char*) です。
omologic

2020/04/20 15:43

あ、確かにそうですね^^; ポインターのポインターを使うケースがいままであまりなかったため、頭が混乱しておりました・・・。 一歩前進、成長出来た気がします! 本当にありがとうございます!
guest

0

なにはともあれ、scanfのデータを受け取るところとか、printfの"%s"に対応するところにポインタが指定されていない時点でどうあがいてもダメでしょ。

2次元配列を考えてて、うまく行かなくて1次元にしたけどなんだか中途半端に前の考えが残っちゃってる、とかいうところかしら?
mallocによる確保の仕方はとりあえずそれでOK。
(追記:本当は失敗したときのチェックをいれるのがお作法)

さてどう格納すべきか。
今のプログラム、どうしようもなく間違っているけどその間違い方からすると
S[0]~0番目の文字列
S[1]~ 1番目の文字列
S[2]~ 2番目の文字列
とか考えてないかしら?
もしそう格納すると、0番めに"Hello", 1番めに"World"を格納すると、
0番めを格納した時点でHello
1番めはS[1]から重なるからHWorld
になっちゃうでしょ。

そこの格納の仕方はこうなるはず。
S[0]~S[L]: 0番めの文字列
S[L+1]~S[2L+1]:1番めの文字列
S[2(L+1)]~S[3L+2]:2番めの文字列
<以下略>
一般化すれば
S[i*(L+1)]~L+1文字分、ということですね。
なので、配列のその場所に取り込むscanfは
scanf("%s",&S[i*(L+1)])あるいは同じことだけどscanf("%s",S+i*(L+1))
対応するprintfは
printf("%s",&S[i*(L+1)])あるいはprintf("%s",S+i*(L+1))
になるでしょう。


あるいは、コンパイラがgccやclangであれば可変長配列が使えるので、要素数Nの配列へのポインタ(つまり二次元配列)を動的に宣言出来るから、この場合は

C

1int main(void){ 2 int N,L; 3 scanf("%i %i", &N,&L); 4 5 char (*S)[L+1] = (char(*)[L+1])malloc(sizeof(char)*N*(L+1));//確保する領域サイズは先と変わらない 6 //これで、S[N][L+1]という二次元配列を確保したことになる 7 for (int i=0; i<N; i++){ 8 scanf("%s",S[i]); 9 } 10<> 11 for(int i=0; i<N; i++){ 12 printf("%s",S[i]); 13 }

というのもあり。


今回はそうする必然性はないけれど、二次元のデータ構造を格納する時のパターンのひとつとしては

int main(void){ int N,L; scanf("%i %i", &N,&L); char** S=(char**)malloc(sizeof(char*)*N);//今度は確保の仕方が違う for(int i=0; i<N; i++){ S[i]=(char*)malloc(sizeof(char)*(L+1)); } for (int i=0; i<N; i++){ scanf("%s",S[i]); } <略> for(int i=0; i<N; i++){ printf("%s",S[i]); }

というのも知っておくべき。ただし、これはCの文法でいう「二次元配列」とは違うというのは覚えておいたほうがいい。(Javaだとこういうのを二次元配列というのがまたややこしい)


最後に、どうでもいいといえばどうでもいいけど、「障害が発生した」という言い方は、コンピュータのシステムを扱っているギョーカイではユーザーじゃなくてシステム側に問題が出たことの意味だったりする。今回の話にはちょっと不適切。


追記。
printfでは書式指定子中に"*"があるとパラメータから取り込んだ数値に置き換えてくれる機能があって、scanfでもそれが使えるような勘違いをしたので修正したわけですけれど、そういうことをしたいのなら

C

1char format[32]; 2int L=10; 3sprintf(format,"%%%ds",L); //結果、formatは"%10s"という文字列になる 4scanf(format,S[i]); //書式指定子は文字列リテラルである必要はない

なんていう手段はあります。

投稿2020/04/19 14:30

編集2020/04/19 23:47
thkana

総合スコア7703

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

SHOMI

2020/04/19 14:53

>配列のその場所に取り込むscanfはscanf("%*s",L,&S[i*(L+1)])あるいは同じことだけどscanf("%*s",L,S+i*(L+1)) 文字数を指定できるprintfの書式指定"%*s"とは異なり、scanfの書式指定"%*s"は読み取っても格納しないことを示しているのでだめですよ。
omologic

2020/04/19 15:06

>thkanaさん ご指摘のとおり、二次元配列をイメージしていたんですが、 領域確保する時点で、一次元配列でしかないことに気付き、インデックスの挙動がうまくわからないまま実装を進めていました。 gccを使っているので可変長配列が使えます。 多くの書籍では可変長配列の方法を紹介していないみたいなので、使わない方がよいのかと思っていました。そうでもないんでしょうか? ポインターのポインターを使って二次元配列風に取り扱うことが出来るんですね 当初この方法みたいなのがあればな、と思っていたので、こちらを試しに実装してみます。
omologic

2020/04/19 15:07

>SHOMIさん scanfは*を使えないの知らなかったので指摘助かりました。 scanfは入力文字数を変数を参照して制限できないということになりますよね?
SHOMI

2020/04/19 15:25 編集

>多くの書籍では可変長配列の方法を紹介していないみたいなので、使わない方がよいのかと思っていました。そうでもないんでしょうか? gccが独自サポートしていた可変長配列(VLA)はC99標準に取り込まれましたが、その後C11でオプションに格下げされました。 C++でもサポートされていません。 そのためでしょうね。 移植性を気にしなくてよい競技プログラミングで使う分には問題ないでしょう。
SHOMI

2020/04/19 15:35

>scanfは入力文字数を変数を参照して制限できないということになりますよね? はい。
thkana

2020/04/19 23:30

scanfの"*"は...失念していました。修正します。 可変長配列は、事実上gccとclangの「ローカル拡張」になってしまっているので、そこで閉じた世界なら(事実上Windows/Microsoft Cが絡まないなら)あり、互換性を気にするなら非推奨、というところでしょうか。競技プログラミングはgccっぽいので紹介しましたが、テクニックの優先順位的に紹介は最後に持っていったほうがよかったかな。 (使えそうだから(?)独自拡張でgccが取り入れて規格化までしたけれどCの拡張に熱心でないMSが突っぱねてオプション規格に落ちた、なんてストーリーなんでしょうかね)
omologic

2020/04/20 15:47

いろいろご解説ありがとうございました。 できるだけ現場準拠で学んでいければと思っております。 まぁ、現場ごとに違うというだろうから、手段は複数持っておけるようにしたいと思います。複数種のやり方を教えていただきありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問