- ベストアンサー
文字列についていい方法はありますか?
マイコンでプログラムを作っています。 配列aに文字を入力し、それを7セグに表示したいです。 表示自体は出来ています。 なので、文字列を配列に入力する方法を教えてください。 (数字以外を表示します。) まず、現在は char *a[6]; a[0]="12345"; seg(*a);//7セグ表示用関数 このようなソースを書きました。これは希望通りの動作してます。 しかし、例えば a[0] = "12"; a[2] = "345"; や、 a[0] = "12" + "345" のように、文字列どうしをくっつけるような使い方が出来ないものかと悩んでいます。 最悪の場合 char a[5]; a[0] = '1'; a[1] = '2'; a[2] = '3'; a[3] = '4'; a[4] = '5'; と、このような方法を使おうと思ってますが、あまり好きじゃないです。 出来ないのは無知なせいなのか、方法は有るが最後の方法が一番マシなのか、もっといい方法があるなら何を使えばいいのか、等アドバイスをお願いします。 よろしくお願いします。
- みんなの回答 (12)
- 専門家の回答
質問者が選んだベストアンサー
#4,#5です。とりとめなくいろいろ書きます。 > その危険と言うのが > strcpy( a, "1234567" ); > となる事があるようにプログラムを書いてしまった時のみの危険なのか、 こっちです。 文字数が分かっているのならstrcpyよりもmemcpyを使用した方が速いかも しれません。 > memcpy( a, "12345", 5 ); 一般的にライブラリ関数は、エラーチェック等のさまざまなオーバヘッドが 含まれるものなので、 スピードを気にするならエラーチェック無しの自作のシンプルな代替関数を 作って速度比較すると良いかもしれません。 #2さん回答のお礼部分を見ると、 「char a[ 6 ];」と「char *a[ 6 ];」の違いが分かっていないような気がします。 二者は明らかにデータ構造が違います。 > 俺も良く分からないんですが、例えば > a[0] = "12"; > a[2] = "345"; > printf("%s",*a); > とやると実行結果は > 12 > となってしまい、345が表示されません。 > > マイコンの場合は > 12??? > と、?の部分には何が入るか分からないような状態になってしまいます。(とりあえず今やったの > でした。) > > たぶんですが、a[0]に"12"のアドレスを渡しただけなので、seg()が受け取った配列にはa[0]の先 > スから5文字分が入力されていて、"345"が行方不明になってるんじゃないかと思います。 これは「char *a[6];」の場合で、「printf( "%s", a[2] );」とすると"345"が表示されると思います。 a[0] → "12" a[1] → 不明 a[2] → "345" a[3] → 不明 a[4] → 不明 a[5] → 不明 です。 やはり意図しているのは、「char a[6];」のほうで、 a[0] → '1' a[1] → '2' a[2] → '3' a[3] → '4' a[4] → '5' a[5] → '\0' こういう構造を望んでいるんじゃないかという気がします。
その他の回答 (11)
- shuyamakawa
- ベストアンサー率67% (111/164)
#4,#5,#8,#11です。本題からそれちゃってすみません。 推測ですが、組み込みのmemcpyは以下のような方法(の組み合わせ)で コンパイラが最適化(高速化)しているのではないかと思います。 (1)関数のインライン展開 関数呼び出し(call)せずに、呼び出し部分に処理を直接展開することで、 関数呼び出しによるオーバヘッドが無くなります。(あまり差は出ないですが) (2)ループ展開 ループ回数が少なく、かつ固定回数の場合、ループ展開してしまうことが 出来ます。memcpyの場合、以下のようにコンパイラが展開してしまうことで 高速化できます。 (これは、ループ回数を、大きい数にしたり、変数で与えたりすることで 無効になるかもしれません。) |memcpy( a, "12345", 5 ); ↓ |a[0] = '1'; |a[1] = '2'; |a[2] = '3'; |a[3] = '4'; |a[4] = '5'; (3)アセンブラのメモリ転送命令に置き換え memcpyに対応するアセンブラのメモリ転送命令があるのかもしれません。
- shuyamakawa
- ベストアンサー率67% (111/164)
#4,#5,#8です。 > >「char a[ 6 ];」と「char *a[ 6 ];」の違いが分かっていないような気がします。 > 違いが分かっていませんでした。 > と言う事は、「char *a[6];」でやってた事は「char *a;」でも同じ動作をしてたと言う事ですね。 そうです。 #8で書いた、 > 一般的にライブラリ関数は、エラーチェック等のさまざまなオーバヘッドが > 含まれるものなので、 > スピードを気にするならエラーチェック無しの自作のシンプルな代替関数を > 作って速度比較すると良いかもしれません。 ですが、 ちょっと作ってみました。 void my_memcpy( char* dst, char* src, int count ) { while ( --count >= 0 ) { *dst++ = *src++; } } int main( void ){ char *a = "0123456789"; char b[ 10 ]; my_memcpy( b, &a[ 2 ], 3 ); printf( "a=%.10s\n", a ); printf( "b=%.10s\n", b ); return 0; }
お礼
ありがとうございます。 作っていただいたmy_memcpyを試してみました。 すると memcpy(a,"1",1);//5クロック my_memcpy(a,"1",1); //45クロック my_memcpy(a,"12345",5); //145クロック となり、strcpyよりも遅いという結果になってしまいました。 my_memcpyのソースを見て思ったんですが、memcpyの5クロックと言うのはc言語の関数の限界を超えていると思います。 実際もっと細かく見てみても mainから関数へジャンプ→4クロック 関数からmainへジャンプ→4クロック と、これだけで既にmemcpyに負けてます。 コンパイラが悪いのか、memcpyがすごいのか、ハード的に特殊な処理をしてるのか分かりませんが、memcpyより早くするのは難しそう(アセンブラで書けば良い?)と言う事がわかりました。
- sis2006jp
- ベストアンサー率0% (0/1)
#7です。 >初期化しておくと何かメリットがあるんでしょうか? char *a[6]; は文字列6個のアドレスを確保したという意味ですから。文字列領域を確保したわけではありません。宣言と同時に初期化をすると、その文字数で領域を確保してくれます。strcpyは確保されている文字数を超えてのコピーは保障してません。エラーの原因となります。 いっそのこと、char a[6][10];とでもしておけば、9文字(1文字は文字列の終了'\0'で使用)の文字列を6個確保したことになります。各文字列はa[0],a[2],・・・a[5]で使えますので、strcpyなりstrcatなり9文字以内なら自由に使えます。他の方も指摘してますがポインタ操作は気をつけないとエラーの原因となります。以上です。
お礼
ありがとうございます。 >文字列6個のアドレスを確保した と >文字列領域を確保した をごっちゃにしてたみたいです。 char a[6][10]; だと、今後表示のバリエーションが増える、とメモリ不足になりそうなので、今回は char a[6]; で行こうと思います。
こんばんわ。 私も質問者さんは、ポインタと配列の事、少々誤解されているように思いますが。それはさておき... もし、表示される文字列がコンパイル時で定数なら、何も a を介す必要は無いのではと思います。seg("12"); や seg("345"); で良いじゃないでしょうか? これなら コンパイル時の演算と言うことで seg("12" "345"); とすば、"12345" と結合もしてくれますよ。 もし、実行時に動的に文字列を構築したいなら、他の方も仰せの通り、strXXX()系か、Xprintf()系の関数を使うのが簡単ですが、質問者さんも仰せの通り、組み込み関係では Xprintf()系自体実装されてない場合もあるようですね。 で、そうなると自分で文字列を構築する関数を用意すると言うのが多分妥当かと思うのですが、真面目に Xprintf() 系の実装なんかはやってられないと思います。 ですが、もしも構築される文字列が数字列に限定されるならこんな安直な実装は如何でしょうか? -- char *to_str(char *dest /* 6byte 以上あること */, int val) { char *p; int d, n; for (p = dest, d = 10000 /* 5桁限定 */ ; d > 0; d /= 10) { n = val / d; *(p++) = (char)n + '0'; val -= n * d; } *dest = '\0'; return dest; } -- こうすれば、char a[6]; とでもしておいて、seg(to_str(a, 123)); とかで、 "123" が得られると思います。
お礼
ありがとうございます。 >表示される文字列がコンパイル時で定数なら、何も a を介す必要は無いのではと思います。 定数ではない場合と、定数の場合があるんです。 全点灯「88888」 バージョン表示「-1.00-」 エラー「E-00 」~「E-99 」 等です。 バージョンとエラーを同じ関数で走らせたいなと思って質問しました。 数字だけの表示は既に作ったのですが、例に挙げていただいたプログラムの方が短いので、参考にさせていただきたいと思います。
- sis2006jp
- ベストアンサー率0% (0/1)
初期化で6つの文字列が代入できます。最大文字数で仮に入れておいたらどうですか。 #include<stdio.h> #include<string.h> int main() { char *a[6]={"12","345"}; printf("%s\n",a[0]); printf("%s\n",a[1]); //連結 strcat(a[0],a[1]); printf("%s\n",a[0]); return 0; }
お礼
ありがとうございます。 >最大文字数で仮に入れておいたらどうですか。 初期化しておくと何かメリットがあるんでしょうか? strcatとstrcpyで何とかなりそうなんですが、初期値を入れておいたほうがいいのであれば入れておこうと思います。
#2です。 文字列の連結には、sprintf関数を使います。 #include <stdio.h> int main(void) { char *a[] = { "12", "", "345" }; char str[20]; sprintf(str, "%s%s", a[0], a[2]); printf("%s\n", str); return 0; }
お礼
ありがとうございます。 sprintf関数はBC++で動かしたら動きましたが、マイコンではコンパイルができませんでした。 原因がちょっと分からないのですが、どうもRAMが足りなくなっているような雰囲気です。 sprintf関数自体はまさに求めていた動作をしていたのでとても残念ですが、これを使うのは諦めようと思います。
- shuyamakawa
- ベストアンサー率67% (111/164)
#4です。補足です。 「char a[6];」で正しいとしたら、ソースはこのようになります。 > char a[6]; > strcpy( a, "12345" ); > seg(a);//7セグ表示用関数 厳密にいうと「strcpy」は、配列境界を越えてしまう危険があり、 それを避けるためには、このように置き換えた方が良いです。 > strncpy( a, "12345", sizeof( a ) ); > a[ sizeof( a ) - 1 ] = '\0';
お礼
ありがとうございます。 とりあえずstrcpyとstrcatを使ってやろうと思います。 そこで一つ質問があります。「配列境界を越えてしまう危険」というのは、ソースの方で5文字を越えないように注意して作ったとしても超えてしまう事があるということでしょうか? 出来れば動作時間の節約、ソースの見易さ、RAM・ROMの節約の為に、ソースで注意すれば何とかなるならやらない方向で行きたいです。 文字列を入力するという事は今のところ無く、今後も無いであろうと思います。 現在の状態を表示する時などに使う予定なので、5文字以上にしないつもりです。(というか、7セグが5桁しかないので必然的にそうする事になります。) その危険と言うのが strcpy( a, "1234567" ); となる事があるようにプログラムを書いてしまった時のみの危険なのか、 strcpy( a, "12345" ); としていても、時々おかしな動作になってしまうほどの危険なのかが知りたいです。 よろしくお願いします。
- shuyamakawa
- ベストアンサー率67% (111/164)
> まず、現在は > char *a[6]; > a[0]="12345"; > seg(*a);//7セグ表示用関数 > このようなソースを書きました。これは希望通りの動作してます。 「char *a[6];」では、ポインタの配列を宣言していますがそれは意図どおりですか? (char型変数を指すポインタ変数が、配列で6個宣言されています。 ) もしかしたら、こうじゃないでしょうか? > char a[6]; (char型変数が、配列で6個宣言されます。) このようにすれば、質問の後半部分も、普通に質問者さんの意図通りに行くんじゃないでしょうか?
お礼
ありがとうございます。 意図通りというか、ポインタにしないと a="12345"; がエラーになってしまったので、とりあえずポインタにしてみたという感じです。 error: a value of type "char *" cannot be assigned to an entity of type "char" こんなエラーが出てしまいます。 Borland C++でもやってみてるんですが、同様に 「移植性の無いポインタ変換(関数 main)」と言うエラーが出ます。
- chirubou
- ベストアンサー率37% (189/502)
私だったら、 void segall( char *str ) { while( str != NULL ) seg( str++ ); } として、 char *mojiretu[] = { "hoge", "moge", NULL }; segall( mojiretu ); とします。これで "hogemoge" というように表示されるハズです。 こういうことがやりたかったのかな?
お礼
ありがとうございます。 とりあえずやってみたのですがうまく動きませんでした。 で、ソースを見る限りだとcharで宣言と同時に文字列を入力してしまっているのでちょっと違うかな?と言う印象です。 ただ、どうにか解読して、少なくとも"hogemoge"と表示させてみたいと思います。
> a[0]="12345"; > seg(*a);//7セグ表示用関数 これが正しく動くのであれば、 > a[0] = "12"; > a[2] = "345"; これも、seg関数の引数として適切な内容を渡せば 正しく動くのではないでしょうか? 引用した2箇所の違いがよくわかりません。
お礼
ありがとうございます。 俺も良く分からないんですが、例えば a[0] = "12"; a[2] = "345"; printf("%s",*a); とやると実行結果は 12 となってしまい、345が表示されません。 マイコンの場合は 12??? と、?の部分には何が入るか分からないような状態になってしまいます。(とりあえず今やったのだと12003でした。) たぶんですが、a[0]に"12"のアドレスを渡しただけなので、seg()が受け取った配列にはa[0]の先頭のアドレスから5文字分が入力されていて、"345"が行方不明になってるんじゃないかと思います。
- 1
- 2
お礼
ありがとうございます。 memcpyを使えばやりたい事が出来ました。 速度もstrcpyとstrcatに比べるとかなり速いのでこれで行こうと思います。 memcpy(a,"12345",5);//9クロック memcpy(&a[0],"1",1);memcpy(&a[1],"234",3);memcpy(&a[4],"5",1);//25クロック strcpy(a,"1");//32クロック strcat(a,"2");//62クロック >「char a[ 6 ];」と「char *a[ 6 ];」の違いが分かっていないような気がします。 違いが分かっていませんでした。 と言う事は、「char *a[6];」でやってた事は「char *a;」でも同じ動作をしてたと言う事ですね。