printf関数の引数指定について

このQ&Aのポイント
  • C言語において、printf関数の引数指定について疑問があります。
  • printf関数では、書式文字列または文字列の先頭アドレスを指定することができます。
  • 文字列の場合、宣言時にchar配列として宣言し、先頭のアドレスを使って文字列を扱うことが一般的です。
回答を見る
  • ベストアンサー

printfの引数指定でなぜ文字列ポインタがOK?

C言語初心者です。現在入門的書籍の1冊目を読んで勉強中です。VBAは経験あります。 printf関数について質問です。 引数の指定で通常  printf("書式文字列", 変数で値) のようにしていしますよね。  char a = 'A';  printf("%c", a); ←ここで変数aの値の「A」を渡している と理解しています。 上の例で、書式文字列を省略し、  printf(a); だとエラーがでます。 しかし、文字列の場合  char a[] = "ABC";  printf(a); はエラーにならず、「ABC」と出力されます。 さらに、ポインタを使用して  char a[] = "ABC";  char *b = a;  printf(b); もエラーにならず、上と同様に「ABC」と出力されます。 まず、この2つの例で、エラーとならず、書式文字列が省略できているのが不思議です。 これが書式文字列を省略しているわけではないとするならば、 「printf(a)」=「printf(b)」=「printf("ABC")」ということになりますが、 「printf(a)」のaも「printf(b)」のbも"ABC"の先頭のアドレスを示しているんですよね。 ということは、printf関数の引数の指定方法は  printf(書式文字列 または 文字列の先頭アドレス, 変数) というように考えられるのですが、認識があっているでしょうか? そもそも文字列について、VBAでは文字と文字列の区別はなにも意識せずに扱えたのでやや戸惑っているのですが、C言語では文字列の場合は宣言時に  char a[] のように宣言し、あとは先頭のアドレスで文字列を使っていくという感じなのでしょうか? 初心者なもので何が理解できていないのかもよくわからない状況で、質問がわかりづらいかもわかりませんが、よろしくお願いします。

質問者が選んだベストアンサー

  • ベストアンサー
  • kmee
  • ベストアンサー率55% (1857/3366)
回答No.3

厳密に言うと、Cには「文字列型」って無いんです。 char型は「文字コードが一つ入る大きさを持った整数」です。 'a'(シングルクオート)は「文字aのコードに相当する整数」です。 文字列は「char型へのポインタを先頭にして、以降『0』になるまでの間にあるchar型の数値の連続」を「文字列」として扱う、という決まりになっています。 "ABC"というのは 'A','B','C',0と連続したchar型の列への先頭アドレス、ということになります。 また、Cでは、ポインタと配列は同列に扱われます。 このあたりの振舞いや、配列とポインタについては、それだけで本があるくらい、Cでは重要なことです。しっかり理解するようにしましょう。 VBでは「文字」と「文字列」が区別が無い、とありますが、正確には「文字」は「長さ1の文字列」なので、違いが無いのも当りまえのことです。 さて、それを踏まえると >  printf(書式文字列 または 文字列の先頭アドレス, 変数) 書式文字列も「文字列」ですから、指定する際にはその先頭アドレスを使用します。なので、printfは単に printf(文字列の先頭アドレス[, ...]) となります。([, ...]は省略可能な値) 書式文字列では、 %で始まる変換書式以外はそのまま出力されます。よって、今回の場合はprintf("%s",b)とprintf(b)が同じ出力になります。 > char a = 'A'; > printf(a); がエラーになるのは、aが数値であってポインタでは無いからです。

palox0505
質問者

補足

丁寧な回答ありがとうございます。 >このあたりの振舞いや、配列とポインタについては、それだけで本があるくらい、Cでは重要なことです。しっかり理解するようにしましょう。 重要ポイントですね。がんばります。 >VBでは「文字」と「文字列」が区別が無い、とありますが、正確には「文字」は「長さ1の文字列」なので、違いが無いのも当りまえのことです。 そういうことなんですね。 C言語を勉強し始めて思ったのですが、VBのことにも理解が深まっていきます。 >> char a = 'A'; >> printf(a); > >がエラーになるのは、aが数値であってポインタでは無いからです。 わかりやすいです! ここで、ちょっと疑問が出てきたのですが、 文字のポインタでprintfに渡すと、  char a = 'A';  printf(&a); →「AフフフフH・」と出力されたのですが、これはどういうことなのでしょうか? 文字列の先頭アドレスがprintfに渡されるので無理矢理 >以降『0』になるまでの間・・・ に続いているアドレスに格納されていた文字が出力されたのでしょうか? さらに、  int a = 9;  printf(&a); ではいくつかの空白が出力され、  int a = 47;  printf(&a); では「/」が出力されたのですが、 まず「9」については「9」というコードに相当する文字が空白で、移行「0」になるまで空白が格納されていた。 「47」については、「/」のコードが「47」で、すぐ後のアドレスに「0」があった。 ということでしょうか?

その他の回答 (5)

  • titokani
  • ベストアンサー率19% (341/1726)
回答No.6

>ということは、printf関数の引数の指定方法は > printf(書式文字列 または 文字列の先頭アドレス, 変数) >というように考えられるのですが、認識があっているでしょうか? もうわかってらっしゃるかもしれませんが、これは違います。 char a[] = "ABC"; printf(a); は、「ABC」と出力されますが、 char a[] = "A%sC"; printf(a); だと、実行時エラーになるでしょう。 これが、 char a[] = "A%sC"; printf("%s",a); だと、「A%sC」と出力されます。 ついでに、 char a[] = "A%sC"; printf(a,"X"); だと、「AXC」ですね。 つまり、第一引数は常に書式文字列ということですね。

palox0505
質問者

お礼

titokaniさんありがとうございます。 >つまり、第一引数は常に書式文字列ということですね。 >printf(書式文字列 または 文字列の先頭アドレス, 変数)」 で「または」としているのがおかしいですね。 第一引数は「文字列」の先頭アドレスで、「文字列」の内容は書式文字列として指定する。 ですね。 「文字列」の先頭アドレスでないとコンパイルエラー 「文字列」内に%sなどのフォーマット指定子を指定した場合は、続けて対応する引数を指定しないと、実行時エラーになる。 とまとめてみたのですがいかがでしょう。

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.5

余談ですが, 文字列リテラルは実際には無名配列になります. つまり #include <stdio.h> int main() { printf("Hello, world\n"); return 0; } と書いた場合, 実質的に #include <stdio.h> int main() { static const char _[] = "Hello, world\n"; printf(_); return 0; } と等価 (ただし配列名は使えない) です. もちろん #include <stdio.h> int main() { static const char _[] = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '\n', '\0' }; printf(_); return 0; } とも (ほぼ) 等価になります.

palox0505
質問者

お礼

Tacosanさんありがとうございます。 なるほどですね。 変数としては使えない状況にあるが、メモリ上では同じように扱われるということですね。 理解が深まります。

  • kmee
  • ベストアンサー率55% (1857/3366)
回答No.4

>  char a = 'A'; >  printf(&a); > →「AフフフフH・」と出力されたのですが、これはどういうことなのでしょうか? > 文字列の先頭アドレスがprintfに渡されるので無理矢理 >> 以降『0』になるまでの間・・・ > に続いているアドレスに格納されていた文字が出力されたのでしょうか? そういうことです。 これもCの特徴なのですが ・明示しない限り、変数やメモリの初期化はしない。したがって、そこに何が書かれているかは保証されない。 今回はたまたまchar aの後にフフフHと読める領域が続いていたのでそう表示された ・配列とかポインタの範囲のチェックはしない VBではdim a(10)としたら a(20)などではエラーになると思います。しかし、Cではa[10]としてもa[20]はコンパイルエラーになりません。実際に動作させても、たまたまエラーにならないこともあります。 > int a = 9; > printf(&a); > ではいくつかの空白が出力され、 > まず「9」については「9」というコードに相当する文字が空白で、移行「0」になるまで空白が格納されていた。 ポインタの「型」は、実体にアクセスするときに、何バイト取りだして、どんな処理をするか、を決めるためのものです。内部的にはただのアドレスであることが多いです。 ここからは、実行環境に依存する話です。 まず、intはsizeof(int)バイト分のメモリを使用します。intが32bitなら4バイトです。 その範囲でどのような順番に並んでいるか、も違います。現在主流のIntel系CPUだと、リトルエンディアンといて、下のバイトが先にくるように並んでいます。つまり、intの 9はメモリ上に 9,0,0,0 と4バイト並んでいることになります。 int型へのポインタの場合、その参照の際には4バイトをセットにして扱いますので、参照した値は「9」になります。 ところが、printfの第一引数では、const char *なので、先頭アドレスは同じでも、各バイト毎に扱うので、 長さ1の文字列で、1文字目のコードが9、と解釈されます。 ここで、やはり現在主流のASCIIコード互換の文字コードを考えると、 0~31は画面には文字を表示せずに特別な意味を持つコントロール文字と呼ばれるものになっています。 9はタブ、または水平タブと呼ばれるもので、現在のカーソル位置から「タブストップ」と呼ばれる位置(通常8文字単位)までカーソルを移動させます。画面上は空白がいくつか並んでいるように見えます。 > 「47」については、「/」のコードが「47」で、すぐ後のアドレスに「0」があった。 同様に 47,0,0,0 とメモリ上にあるので「長さ1の文字列で1文字目が「/」」と解釈されています。

palox0505
質問者

お礼

すっきりしました! 実に明快な回答をありがとうございます! >これもCの特徴なのですが >・明示しない限り、変数やメモリの初期化はしない。したがって、そこに何が書かれているかは保証されない。 >今回はたまたまchar aの後にフフフHと読める領域が続いていたのでそう表示された >・配列とかポインタの範囲のチェックはしない >VBではdim a(10)としたら a(20)などではエラーになると思います。しかし、Cではa[10]としてもa[20]は >コンパイルエラーになりません。実際に動作させても、たまたまエラーにならないこともあります。 この辺がCが難しくもあるが単純明快でもあるという理由なのかな?という感じがしました。 Cの魅力を垣間見れた気がします。 勉強を進めていきます!

noname#140045
noname#140045
回答No.2

>printf(書式文字列 または 文字列の先頭アドレス, 変数)…認識があっているでしょうか? 合っていると言えば、合っています。 が、もっと正確に言えば、printf()で最初の引数は「文字列の先頭アドレス」であって、書式文字列も、それは文字列の先頭アドレスの1種に過ぎないのです。 (書式文字列の実体は別に領域がとられ、その書式文字列の先頭アドレスが引き渡される) また、C言語では「文字列」であるかどうかが問題であって、そこに書式が定義されているかは問題ではありません。 つまり、 printf("%s"); などとすれば、コンパイルエラーとはなりませんが、実行時に大抵は異常終了します。

palox0505
質問者

補足

みなさん早くに回答恐縮です。 ありがとうございます。 この問題はprintfの引数がどうたらというより、C言語における文字列の問題ですね・・・ >が、もっと正確に言えば、printf()で最初の引数は「文字列の先頭アドレス」であって、書式文字列も、それは文字列の先頭アドレスの1種に過ぎないのです。 >また、C言語では「文字列」であるかどうかが問題であって、そこに書式が定義されているかは問題ではありません。 わかってきたような気がします。 書式文字列 または 文字列の先頭アドレス・・・はどちらも同じで、文字列の先頭アドレスなんですね。 >つまり、 >printf("%s"); >などとすれば、コンパイルエラーとはなりませんが、実行時に大抵は異常終了します。 これは一応文字列が指定された形にはなっているのでコンパイル時にエラーとはならないが、 実行時に、実際何を%sにするの?何もないじゃん。ということでエラーとなり異常終了する。 ということでしょうか?

  • koko_u_u
  • ベストアンサー率18% (216/1139)
回答No.1

> 「printf(a)」=「printf(b)」=「printf("ABC")」ということになりますが、 そうです。 つまりコンパイラには "ABC" という「文字列」がその先頭アドレスのように見えている、ということです。 printf() のプロトタイプ宣言を確認しましょう。

palox0505
質問者

お礼

こんなに早く回答が頂けるとは! ありがとうございます! >> 「printf(a)」=「printf(b)」=「printf("ABC")」ということになりますが、 > >そうです。 >つまりコンパイラには "ABC" という「文字列」がその先頭アドレスのように見えている、ということで。す。 なるほど、コンパイラは文字列を先頭アドレスで扱うんですね? >printf() のプロトタイプ宣言を確認しましょう。 まだヘルプとかの見方が慣れなくて・・・勉強します。

関連するQ&A

  • Cでは文字列をどのように認識するのでしょうか?

    C言語には文字列型というものは存在しないと教わりました。 文字列の終わりはヌル文字で認識できますが、 文字列型というものが存在しないのに何故次のバイトを読もうとするのですか? たとえばchar str[] = "abc";、あるいはchar *p = "abc";とあったとして printf(str);あるいはprintf(p); でなぜabcが出力されるのでしょうか?なぜaの次にbがbの次にcがあるとわかるのでしょうか? char型で先頭アドレスが渡された場合、ヌル文字を見つけるまでアドレスをインクリメントし続けるという決まりでもあるのでしょうか? それに文字列型というものが存在しないなら''と""を分ける意味もないのでは??

  • 文字列を表すための配列とポインタ

    文字列を表すための配列とポインタ  配列とポインタは同様に扱えるもの、と思って、次のプログラムを作りました。処理系は、Visual Studio 2010 コマンドプロンプトです。 #include <stdio.h> void main(void) { char a[256]; char *b; printf("文字列を入力してください。\n"); printf("例「abcde」\n\n"); printf("配列型文字列を使います。\n"); scanf("%s", a); printf("文字列は%sです。\n\n", a); printf("ポインタ型文字列を使います。\n"); scanf("%s", b); printf("文字列は%sです。\n", b); }  すると、まずコンパイル時に、 「warning C4700: 初期化されていないローカル変数'b'が使用されます」 と表示されました。そして、実行すると、「配列型文字列」の方は問題ないのですが、「ポインタ型文字列」の方の実行後に、 「x.exeは動作を停止しました。 問題が発生したため、プログラムが正しく動作しなくなりま した。プログラムは閉じられ、解決策がある場合は Windowsから通知されます。」 と表示され、エラーとして終了してしまいます。 「char *b;」 と宣言するところが問題のようですが、なぜなのかが分かりません。どなたか、解説をお願いします。

  • printf関数の引数は文字例と決まっているからです。

    printf関数は文字列しか引数として受け取りません。 printf("文字列"); では数値を引数にしたらどうでしょう? #include <stdio.h> #main () {     printf(1);     return 0; } これはコンパイルエラーになります。printf関数の引数は文字列と決まっているからです。 この文章について質問です。 Q.なぜ上の「文字数」はダブルクォーテーションで囲まれているのに、下の「1」にダブルクォーテーションはないのでしょうか? Q.文書を見る限り、Cでは文字列と数値は区別されているように見えますが、なぜでしょうか?

  • ポインタにによる値の表現と文字列の表現について

    ◎1------------------------------ #include<stdio.h> int main(void) { char *pt="ABC"; printf("pt=%s\n",pt); char dt[10]="ABCDE"; char *pp; pp=dt; printf("pp=%s\n",pp); return 0; } -------------------------------------- ◎2---------------------------------- #include<stdio.h> int main(void) { char *pt="ABC"; printf("*pt=%s\n",*pt); char dt[10]="ABCDE"; char *pp; pp=dt; printf("*pp=%s\n",*pp); return 0; } ----------------------------------- ◎3--------------------------------------- #include<stdio.h> int main(void) { int ary[5]={111,222,333,444,-1}; int* pt=ary; while(1){ printf("%d ",*pt); ++pt; if(*pt==-1){ break; } } puts(""); return 0; } ----------------------------------------------- 以上3つのプログラムで、◎1はprintfで「*」が付いてなく、正常に実行出来ました。 ◎2はprintfで「*」が付いてなく、エラーは出ませんが、文字列が表示されませんでした。 ◎3は文字列ではなく値ですが、printfで「*」が付いていて正常に実行できます。 これは、値の場合は「*pt」とすることで、ptのアドレスに値を代入しているという事で、「printf("%d ",*pt);」で実行できたということですかね? 文字列の場合は、先頭のアドレスを渡すだけなので、「printf("pt=%s\n",pt);」のようにしてアドレスを参照しないとダメであるということですか? ◎2で「printf("*pt=%s\n",*pt);」としてしまうと、何が起きてしまうのかわかりません。 以上、教えていただけると嬉しいです。

  • メソッド間の文字列の受け渡し

    初歩的な内容ですが、混乱してしまっているので、どなたか教えてください。 呼び元のメソッドで文字列を設定した文字列を呼び先subで取り出したいのですが、 アドレスから文字列を取り出すにはどうすればいいのでしょうか? #include <stdio.h> main() { char abc[]="ABC"; printf("%s\n", abc); sub(abc); /* 文字列abcのアドレスを値渡し */ printf("%s\n", abc); } sub(char *abc) /* 文字列abcのアドレスをポインタとして値受け取り */ { }

  • 配列やポインタに文字列を設定することについて

    ◎1------------------------- #include<stdio.h> int main(void) { char ss[80]; scanf("%s",ss); printf("%s\n",ss); return 0; } ---------------------------- ◎2--------------------------- #include<stdio.h> int main(void) { char *ss="abcde"; printf("%s\n",ss); return 0; } ------------------------- ◎3---------------------- #include<stdio.h> int main(void) { char *ss; ss="abcde"; printf("%s\n",ss); return 0; } ------------------------- 以上3つプログラムで疑問をいだいたのですが、 まず◎1で、これは例えば、 cahr ss[80]="abc"; のように配列ssに文字列"abc"そのものを入れているのか、 char *ss="xyz"; のようにまず"xyz"という文字列をメモリ上のどこかに設定し、その先頭番地をssに代入しているのか、どちらの考えでいいのかわかりません。 次に、◎2、3ではどちらも正常に実行できたのですが、特に◎3で「ss="abcde";」と記述していますが、ssにはアドレスを代入するという認識かあるのですが、文字列定数を代入しても問題ないのか?という疑問があります。 教えていただけたら嬉しいです。

  • printf() だけで文字列を表示できる??

    1: int printf(char *, ...); 2: main() 3: { 4: printf("Hello, world.\n"); 5: } C で書かれた上のプログラムが正常にコンパイルでき、しかもHello, world. と表示することが理解できません。どなたか教えていただけないでしょうか。 1行目でprintf のプロトタイプ宣言を書き、その実体は書いていません。これをコンパイルしたとき、「printf() は知らない」と言われると思いましたが、そうはならず、Hello, world. を表示しました。 また、1行目の関数宣言の引数を変更すると、「ビルトイン関数と型が一致しない」と言われます。その「ビルトイン関数」というのは文字通りビルトインされていて任意に使用できるようになっているのでしょうか。 コンパイラはgcc version 3.4.4 です。

  • 引数のポインタ変数をローカル変数に設定方法

    引数で受け取ったポインタ変数を受け取った関数内のローカル変数に設定するには、 どうしたらよいでしょうか? イメージとしては、 int abc(char *a){ *a = '3' ローカル変数b = *a }

  • 指定した文字列を探して・・・

        第1引数の文字列中に第2引数で指定した文字がある場合に、その     文字のあるアドレス(ポインタ)を返す関数doko()作成し、     プログラムを完成させよ。     指定した文字がない場合はNULL(ヌルポインタ)を返すものとする。     配列の[](カギカッコ)を利用しないで作る事     main内は変えないこと いろいろと模索したのですが、strcpyなどは使うのでしょうか? 参考サイトを見たりしたり、ほかの質問などを見たのですが なかなか理解できずに現在にいたってます 以下のコードのdokoは沢山やりすぎたので一番シンプルな間違い方をしています 何かご指摘あればお願いします #include <stdio.h> #define MAX 128 char* doko(char *,char); int main() { char buf[MAX]; char *p = "abcdef12345"; char *r; r = doko(p, 'f'); if (r == NULL) { printf("mojiretsu ni f toiu moji ha arimasen\n"); return 0; } printf("%s (= f12345)\n", r); scanf("%s", buf); r = doko(buf, 'x'); if (r == NULL) { printf("mojiretsu ni x toiu moji ha arimasen\n"); return 0; } printf("%s\n", r); return 0; } char* doko(char *p,char a) { while(*p){ if(*p == a){ return p; } if(*p != a){ return NULL; } ++p; } }

  • 文字列のswap

    文字列をswapするプログラムを作りたいと思っています。 やってみると、最初の一文字しかswapされません。 どうしてこれではいけないのでしょうか? よろしくお願いします。 以下が作ったプログラムです。 #include<stdio.h> void swap(char *a,char *b); int main() { char a[2][5]={"abc","efg"}; printf("befor a[0]:%s a[1]:%s?n",a[0],a[1]); swap(&a[0],&a[1]); printf("after a[0]:%s a[1]:%s?n",a[0],a[1]); } void swap(char *a,char *b) { char *tmp; tmp=*a; *a=*b; *b=tmp; } 実行結果は befor a[0]:abc a[1]:efg after a[0]:ebc a[1]:afg となります。 befor a[0]:abc a[1]:efg after a[0]:efg a[1]:abc となって欲しいです。

専門家に質問してみよう