こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

strchr() の第2引数はなぜ int 型なのでしょうか

もしかすると、ちょい前の質問(https://okauth.okwave.jp/qa4151232.html)と同じことを聞いているような気もしますが、気にせずポスト。

その質問を読んだ時に strchr() のマニュアルを見たわけなんですが、そのプロトタイプ宣言は

char* strchr(const char* s, int c);

なんですね。どうして第 2 引数の型が int なのでしょう?「文字」c を検索するんだから普通に考えれば char ではないかと思うのですが、誰か教えて下さい。

ソースはこんな感じだったので、int である必要はないように思えるのですがどうなんでしょうか?
負数を与えたときに「何らかの動作」を期待してのことなのでしょうか?

char* strchr(const char* s, int c)
{
 char ch;
 ch = c; /* <= 結局 char 型にしている */
 for ( ; ; ++s) {
  if ( *s == ch ) {
   return (char*)s;
  }
  if ( *s == '\0' ) {
   return NULL;
  }
 }
}

投稿日時 - 2008-07-04 21:19:49

QNo.4151525

暇なときに回答ください

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

C++やC99では関数を呼び出す際には関数原型が必須ですが、以前の規格では可変個引数を持つもの(printfなど)を除けば、標準関数は関数原型なしで呼び出してもよいことになっていました。

関数原型なしで関数を呼び出した場合、既定の実引数拡張によって、char型はintまたはunsigned int型に暗黙的に型変換されます。これがstrchrやisalphaなどの引数がcharではなくintであることの直接の理由だと思います。

投稿日時 - 2008-07-05 14:17:22

お礼

回答ありがとうございます。

>関数原型なしで関数を呼び出した場合、既定の実引数拡張によって、
>char型はintまたはunsigned int型に暗黙的に型変換されます。

つまり、strchr() が作成された当時はプロトタイプ宣言などなく、いきなり

strchr("Hello, world", 'o');

のように呼び出されて、第2引数は自動的に int にキャストされてしまうので、
どうせキャストされるんだから strchr(const char*, int); でいいっだろう。

ということですかね。

投稿日時 - 2008-07-05 15:14:49

ANo.3

このQ&Aは役に立ちましたか?

30人が「このQ&Aが役に立った」と投票しています

回答(12)

ANo.12

またしても無駄な蛇足が増えて必要な補足が足りず、、、すみません。

> 「文字」をメモリの中から区別して表現するためには char では足らない状況があったため、
『「文字」を(文字以外の何かを含むかも知れない)メモリの中から区別して表現するためには char では足らない』状況があったんじゃ無かろうか、それにあわせてintになっているのではないか、と言う推測でした。

ちなみにワードマシンのくだりは以下のページなどを参考にしてますが、実際に確認したわけではないので#9とどちらが正しいのかはわかりません。
http://www.amy.hi-ho.ne.jp/~lepton/program/p5/prog512.html

> スタックポインタ(IA-32ではESPレジスタやEBPレジスタ)はレジスタは指しません
これについては解釈を間違えてたようです。すみません、指摘ありがとうございます。> #10

関数に渡す際に拡張される件についてはJIS X3010:2003 の 6.5.2.2 関数呼び出し から一部抜粋
呼び出される関数を表す式が、関数原型を含まない型を持つ場合、各実引数に対して整数拡張を行い、型floatを持つ実引数は型doubleに拡張する。
(中略)
呼び出される関数を表す式が関数原型を含む型を持つ場合、実引数は代入と同じ規則で、対応する仮引数の型に暗黙に変換する。

と書いてて気づきました。#11の「C90では、可変個引数を持つものを除き、標準ライブラリ関数を関数原型(プロトタイプ)なしで呼び出してもよいことになっています。」が仕様として定められていたなら(かつ、抜粋部分も同等ならば)、整数拡張が入るケースを想定しなければならないためintにしておかなければならないという説は頷けます。
# JIS X3010 : 2003 では見つけられませんで、古い規格も見られないようなので裏は取ってませんけど。
## includeなしで標準関数使っても怒られないのは処理系の独自仕様だと思ってた

投稿日時 - 2008-07-08 23:35:34

お礼

>またしても無駄な蛇足が増えて必要な補足が足りず、、、すみません。
いえいえ、回答していただけて助かっております。

>『「文字」を(文字以外の何かを含むかも知れない)メモリの中から区別して表現するためには
> char では足らない』状況があったんじゃ無かろうか、
>それにあわせてintになっているのではないか、と言う推測でした。
「文字」を実際にどのようなビット列として格納するのかを気にしなくて済むのが「高級言語」の本文ですよね。
# 実際に気にしなくて良いかどうかはさて置き

>整数拡張が入るケースを想定しなければならないためintにしておかなければならないという説は頷けます。
今回の質問の回答としては、それが最終的な結論のようですね。
わからんままに補足で質問したために混乱を招いたかもしれませんが。

投稿日時 - 2008-07-09 10:25:56

ANo.11

> 結局(charで表す事ができるはずの)'A'がcharなのかintなのか。

今までの回答では、誰もがみなintだと書いていると思います。
'A'のような単純文字定数はint、L'A'のようなワイド文字定数はwchar_tです。より詳しく知りたいなら、JIS X3010:2003の6.4.4.4 文字定数を参照してください。

>  ここについての私の見解は、単純にこの関数を規定した人(たち)が「仮引数の型の決め方が理にかなっている」よりも「getchar()なんかで文字を扱う仮引数はintばっかりなのでこれもintで良いジャン」を優先させた結果じゃないかなと思う。

> そもそもここってC言語の規定じゃなくってCライブラリの設計理念の話だよね。
>  いや、もちろん標準CライブラリうんぬんってのもC言語の言語仕様とともにどこかで規定されてるんだろうけど。

標準ライブラリはC言語の規定です。
詳しくは、JIS X3010:2003の7. ライブラリを参照してください。

> そういう意味では「(char *, int)だったらK&R仕様のコンパイラでも通るようになるじゃないか」と、ものすごく古いところまでの互換性まで考えてこうなったのかも知れないね。

C90では、可変個引数を持つものを除き、標準ライブラリ関数を関数原型(プロトタイプ)なしで呼び出してもよいことになっています。少なくともC90(というよりC89)が制定された時点では、世の中に存在する処理系は標準化以前のものばかりだったわけで、ものすごく古いという考え方は当てはまりません。
関数原型なしで、例えば、

char *strchr();
int main()
{
char *p = strchr("abc", 'c');
return 0;
}

のような使い方ができるようにするには、標準関数の仮引数はcharやshortでは駄目で、intでなければなりません。
C90に、floatを引数に持つsqrtfなどがないのも同じ理由からです。
# AMD1からは、wchar_tを取る関数があったりして、混乱が見られますが...

getcharやfgetcの返却型がintなのはEOFを表現できる必要があるからですが、strchrの第2引数がintなのはそんな理由ではないのです。
isalphaの引数がintなのも、EOFを表現できるようにというよりは、まずはintでなければならないからで、intならEOFでもよいことにしようというのは後付ではないかと思います。

> 「charであっても文字を全て表す事ができるとは限らない」は真にはならないでしょう。

漢字などの多バイト文字だけは例外ですね。

投稿日時 - 2008-07-07 07:05:36

お礼

回答ありがとうございます。

>のような使い方ができるようにするには、標準関数の仮引数はcharやshortでは駄目で、
>intでなければなりません。
>C90に、floatを引数に持つsqrtfなどがないのも同じ理由からです。

普段、プロトタイプ宣言なしで関数を使用することはないので、
今回の一連の回答で非常に勉強になりました。

投稿日時 - 2008-07-09 10:15:10

ANo.10

 うわ。伸びてますな。ううん・・・・本当は議論のようなレスの繰り返しはNGなんだけど、最終的な結論を出すためにあえて踏み込んでみたい。

> C++では'A'はchar型ですが、'AB'のような多文字リテラルはint型になります。
 便乗質問で申し訳ないけど、ここをすっごく知りたい。結局、今までの話の流れではC++ではどうだ、多文字ではどうだという話ばかりで「C言語の仕様において'A'という単文字リテラルがcharなのかintなのか」という事が正確になってない。これは是非専門家の方に解説していただいてやね。より正確で詳しいQ&Aになれば良いんじゃないかなぁなんて。C++の話はとりあえずおいておいて、"abc"などの文字列リテラルや'AB'などの多文字リテラルも省いて、結局(charで表す事ができるはずの)'A'がcharなのかintなのか。一応GCC 3.3.6 releaseではsizeof('A')=4になった事を報告しておきます。これは処理系依存じゃなくてきちんと規定されてる内容の実装って事で良いのでしょうかね?

> strchr() のように「文字しか想定していない」状況でも int 型が使用されているのが不自然に思えたので、今回質問させていただきました。
 ここについての私の見解は、単純にこの関数を規定した人(たち)が「仮引数の型の決め方が理にかなっている」よりも「getchar()なんかで文字を扱う仮引数はintばっかりなのでこれもintで良いジャン」を優先させた結果じゃないかなと思う。そもそもここってC言語の規定じゃなくってCライブラリの設計理念の話だよね。
 いや、もちろん標準CライブラリうんぬんってのもC言語の言語仕様とともにどこかで規定されてるんだろうけど。そういう意味では「(char *, int)だったらK&R仕様のコンパイラでも通るようになるじゃないか」と、ものすごく古いところまでの互換性まで考えてこうなったのかも知れないね。

 文字をcharで表す事ができるか否かについては、ANo.9さんに同意。文字を十分表す事ができる型をcharと定義する事とした、というのが正しくて、仮にワードマシンというものがあったとしても、それはバイトとワードが同一長になる(例えばcharもintも32ビットになる)だけの話で、命題「charならば文字を全て表す事ができる(←これはC言語の定義です)」の逆にも裏にも対偶にもなっていない「charであっても文字を全て表す事ができるとは限らない」は真にはならないでしょう。

 IA-32とか超得意分野。まず、レジスタで計算するのは当然の話で、基本的にはMOV命令→ADD系命令の繰り返しで計算していきます。いや、ADD系命令でメモリtoメモリという命令もあるんだけど、私はコンパイラの出力では見たことないと思う。この辺に関してはi80386(というかi8080)からP6まで何も変わってません。NetBurstやCoreマイクロアーキテクチャは分からないけど・・・・。
 で、IA-32の場合、ALレジスタ(8bit)、AXレジスタ(16bit)、EAXレジスタ(32bit)というものがあって(母体は同じ)、このうちcharからcharへの代入はMOV AL, mem→MOV mem, ALという命令になり、8ビットで動作している事が分かります。IA-32ではアドレッシングはワード単位にしないと効率が悪く(=命令処理ステップ数が多く)なりますが演算はバイト単位でも問題ありません。

 後、ごめんなさいANo.2の記述に誤りがありました。80x86のPUSH命令には8ビットで積む命令がありませんでした(出典:80x86/x87ハンドブック(ナツメ社))。MS-Cコンパイラの#pragma packコンパイラ命令(これは構造体の配置を制御する奴)と混同していたようです。なので、少なくともIA-16やIA-32において8ビットでスタックに詰まれるという事は無いようです。ただ、だからと言ってCコンパイラがintで積む訳ではないというのはお間違えなく。ん? 同じ事か? ん~・・・・ここは専門家の意見を仰いだ方が・・・・。

 また、スタックポインタ(IA-32ではESPレジスタやEBPレジスタ)はレジスタは指しませんし、ワード単位でしか動けないという事はありません。でないとint v[10]; int *a=v[0]; a++;が実際はaが4増える(VC++4.0 32bit版以降)という理屈が成り立たないよね。char v[10]; char *a=v[0]; a++;が1増えるのでバイト単位の移動ができてるはずだもんね。

 あれこれ書きすぎて長文で申し訳ないね。

投稿日時 - 2008-07-07 02:15:47

お礼

たびたびの回答ありがとうございます。

>本当は議論のようなレスの繰り返しはNGなんだけど
いえいえ、そのために長めに質問を開けていたりするので。

>「C言語の仕様において'A'という単文字リテラルがcharなのかintなのか」という事が正確になってない。
他の方の回答にあるように、C言語の規約では int 型のようですね。
ずっと char 型だと思ってました。

>単純にこの関数を規定した人(たち)が「仮引数の型の決め方が理にかなっている」よりも
>「getchar() なんかで文字を扱う仮引数はintばっかりなのでこれもintで良いジャン」を
>優先させた結果じゃないかなと思う。
私も漠然とそんな印象でいました。

>そもそもここってC言語の規定じゃなくってCライブラリの設計理念の話だよね。
そうです。ライブラリを設計する上では、第2引数は int でも char でも良いような気もしましたが、
C言語の規約からの要請を受けて int に「わざわざ」しているとの助言を得ることが今回できました。

投稿日時 - 2008-07-09 10:11:56

ANo.9

> 「文字」をメモリの中から区別して表現するためには char では足らない状況があったため、統一的に int を使っているのだと思う、と言うことです。

多バイト文字を除けば、文字を表現するのにcharで足りないという状況はありません。
JIS X3010:2003の6.2.5 型から引用すると...

型charとして宣言されたオブジェクトは, 実行基本文字集合の任意の要素を格納するのに十分な大きさを持つ。実行基本文字集合の任意の要素をchar型のオブジェクトに格納した場合, その値は非負であることを保証する。その他の文字をchar型のオブジェクトに格納した場合, その結果の値は処理系定義とするが, その型で表現可能な値の範囲に含まれなければならない。

となっていることが理由です。

> char は 1byte ですが、マシンの最小処理単位が 1byte ではない環境(ワードマシン)があることが原因じゃないかな、と。

例えば、アドレスが16ビット単位の場合、charを16ビットとして実装するのが普通です。どうしてもcharを8ビットにしたい場合、char*はアドレスのほかにワードの上位か下位かを表すビット情報が含まれることになります。
つまり、これはcharで文字を表現できない理由にはなりません。

> まず 'A' が int と言うのは事実です。

これはその通りです。
C++では'A'はchar型ですが、'AB'のような多文字リテラルはint型になります。

> で、x + y がいったん int になる、と言うのは整数拡張のことを指していると思われます。(これは言語仕様としてはC99からかな?)

整数拡張というよりは「通常の算術型変換」によるものです(通常の算術型変換の過程で整数拡張が行われることがあります)。
整数拡張(integer promotion)という用語はC99のものですが、以前は汎整数拡張(integral promotion)という用語が使われていただけで、内容は同じです。

> が、x = y や関数の引数として char を渡したときに int に拡張される、というのは処理系依存だと思います。

処理系には依存しません。
ちなみに、x + yのときにintに拡張されるかどうかは処理系に依存します。なぜなら、charが符号無しで、charとintのビット数が同じ場合はunsigned intに拡張されるからです。

投稿日時 - 2008-07-06 10:58:44

お礼

かような重箱の隅をつつく質問に回答いただきありがとうございました。

普段、標準関数は「そういうもんだ」と思って使っていたので、今回改めて勉強になりました。

投稿日時 - 2008-07-06 22:05:54

ANo.8

#5です。

ちょっと書き方が足らなかったようで誤解されてしまったかな。
int にキャストされてしまうことが悪いのではなくて、「文字」をメモリの中から区別して表現するためには char では足らない状況があったため、統一的に int を使っているのだと思う、と言うことです。
char は 1byte ですが、マシンの最小処理単位が 1byte ではない環境(ワードマシン)があることが原因じゃないかな、と。

以降は結構怪しい蛇足。(間違っていたら指摘していただけると助かります)
#6 についてですが…まず 'A' が int と言うのは事実です。
で、x + y がいったん int になる、と言うのは整数拡張のことを指していると思われます。(これは言語仕様としてはC99からかな?)
が、x = y や関数の引数として char を渡したときに int に拡張される、というのは処理系依存だと思います。
# 正確には int と言うわけではないけど

たとえばIA-32のCPUだと演算にはレジスタを使いますが、このレジスタのサイズが32bitあるため、x = y は(実際には何も演算しないけど)y を演算用に一旦32bitに拡張してレジスタへ格納し、x にコピーするオブジェクトを生成することがあります。
関数に渡すときは引数をスタックに積むのですが、これも同様な理由でスタックを指すスタックポインタがレジスタしか指せない(?)ため、前述の理由で一旦拡張されます。
と言うわけで、実際の動作としてそうなることはあるが、それは言語として決まっているのではなく、マシンに対応するために処理系がそうしているだけ、と思うのです。

投稿日時 - 2008-07-06 04:57:55

補足

>「文字」をメモリの中から区別して表現するためには char では足らない状況があったため、
>統一的に int を使っているのだと思う、と言うことです。

「文字」を表現する場合にはどんな処理系でも char で足りていると思っています。
1文字が 1 byte でない処理系では「それなりに」char 型が定義されているのではないでしょうか。

前回の回答にあった getchar() は「文字ではない」EOF をカバーするために int 型を利用しているというのが私の理解です。

strchr() のように「文字しか想定していない」状況でも int 型が使用されているのが不自然に思えたので、今回質問させていただきました。

投稿日時 - 2008-07-06 12:00:42

ANo.7

> x=yも、yをintに拡張してその下位1バイトを取ってxに代入します

そんな仕様はありません。
JIS X3010:2003の6.5.16.1 単純代入から引用すると...

意味規則 単純代入(simple assignment)(=)は, 右オペランドの値を代入式の型に型変換し, 左オペランドで指し示されるオブジェクトに格納されている値をこの値で置き換える。

となっています。
char x, y;に対して、sizeof(x = y)が1であることから、代入式の型がchar型であることは明白です。つまり、intを介さずに直接charに型変換されることになります(この場合は、同じ型なので変換なし)。

> つまり、Cでは関数にchar型の値を渡すことは不可能で、自動的にintになってしまいます。それを受ける仮引数もintの必要があると言うことです。

そんな仕様もありません。
同じく6.5.2.2 関数呼出しから引用すると、

実引数は, 任意のオブジェクト型の式でよい。関数呼出しの準備の段階で, 実引数を評価し, 各実引数の値を対応する仮引数に代入する。

となっています。代入の際に、無意味なintへの変換がないことは上述の通りです。
ただし、関数原型がない場合は話が別です。

投稿日時 - 2008-07-06 00:51:47

お礼

徐々に理解が進んでいるような気もする koko_u_ です。

文字リテラルは int 型なので、

char ch = 'a';

とした時に、情報が削られて char 型になり、プロトタイプ宣言なしで

strchr("Hello, world", ch);

と呼び出されると、再び ch が int 型に自動的にキャストされると理解しました。

strchr("Hello, world", 'a');

と呼び出された場合は、そもそも第2引数は int 型であるということですね。

なので、strchr() は「呼び出される時」は strchr(const char*, int) であり、
実際には第2引数として「文字」が渡されることを想定しているので、
関数の中で第2引数を char 型に削って使用していると理解しました。

投稿日時 - 2008-07-06 10:18:18

ANo.6

>>Cの場合は、charの値というのは書いた瞬間にintになりますので

他の方の回答で解決したのかもしれませんが。以下、Cの場合です。
まず、char型の定数はありません。'A' はint型の定数です。これはいいですよね。
char x,y; とすると、 x+y という式を書くと、xがintに拡張されて、それとyがintに拡張した値と足します。
x=yも、yをintに拡張してその下位1バイトを取ってxに代入します(コンパイラは最適化したコードを出すのでこうはならないですが、理論上こういうことです)。

つまり、Cでは関数にchar型の値を渡すことは不可能で、自動的にintになってしまいます。それを受ける仮引数もintの必要があると言うことです。

投稿日時 - 2008-07-05 19:03:19

お礼

たびたびの回答ありがとうございます。

>まず、char型の定数はありません。'A' はint型の定数です。これはいいですよね。
うは。初めて知ったよ。ずっと char 型だと思ってたよ。
strchr("Hello, world", 'o') は正しく strchr(const char*, int) の呼び出しだということですね。

投稿日時 - 2008-07-06 00:31:56

ANo.5

#2に概ね同意です。
c = getchar();
if (c != EOF)
p = strchr(buf, c);
のような連携が楽だからでしょう。

背景として char は「文字」ではなくて「文字を入れるのに十分なサイズを持った整数」であることとか、char が signed / unsigned どちらなのか処理系依存なこととかがあって、何かの中から文字を区別するためには int を使う慣習になっているのだと思います。(それが char で十分である場合も、そうすることで無用な型変換を避けられる)
# is*** 系が int なのは unsigned char と EOF をカバーする必要があるためです。

参考URL:http://www.opengroup.org/onlinepubs/009695399/

投稿日時 - 2008-07-05 18:03:40

お礼

回答ありがとうございます。

>それが char で十分である場合も、そうすることで無用な型変換を避けられる
皆さんの回答でようやくわかってきました。
プロトタイプ宣言がないと自動的に int にキャストされてしまうことが諸悪の根源(?)であるようですね。

投稿日時 - 2008-07-06 00:16:01

ANo.4

#3です。

> 第2引数は自動的に int にキャストされてしまうので、
> どうせキャストされるんだから strchr(const char*, int); でいいっだろう。

「~でいいだろう」ではなく、「~でなければならない」です。
実引数の型と仮引数の型が矛盾する場合の動作は未定義ですから。

投稿日時 - 2008-07-05 16:25:02

お礼

>実引数の型と仮引数の型が矛盾する場合の動作は未定義ですから。
なるほど。
必ず仮引数の型にキャストされると思っていました。

投稿日時 - 2008-07-06 00:08:18

ANo.2

 charが慣習的にintで表される由来はint getchar()でしょうね。charはC言語において、「ターゲットとするプラットホームで扱う全ての文字を十分表現できる」大きさと規定されている(参照:プログラミング言語C第2版 pp.44)。ところで、EOFは文字ではない。つまり、getchar()が返す値群は、上で言った文字+EOF分の表現ができないといけない。charが7ビットである処理系(レアケースなんかじゃないですよ。その当時は普通に存在してたんですから。ちなみに「1バイト」とは「8ビット」ではありません。2008年現在でも、1バイトが9ビットなマシンなんかも当たり前のように存在します。)を考えてみると7ビットなので128文字。これにEOFを加えた129文字をgetchar()は表現できる必要がある。なのでcharではなくintになっています。そこから文字1つは慣習的にEOFを想定のうちに入れたintになってるんじゃないかね。EOFが絶対来ない様な場面でも。

> Cの場合は、charの値というのは書いた瞬間にintになりますので、関数の仮引数でchar型というのはあり得ません。
 あれ? そうだったかな・・・・? NEC版 MS-DOS 3.30C+LSI-C/86ではAXレジスタではなくALレジスタを使うコードを吐いていたような・・・・? たしかTurbo-C++ Ver.1.01も(こっちは違うかも)。K&R(いわゆるC言語バージョン0.9)の頃は確かに全てのcharをintに直して受け渡した後でcharに戻していたけど(というかK&Rの頃はプロトタイプ宣言そのものが無かった)、ANSI Cではcharを暗黙にint扱いしないはず(なのでK&Rを想定したソースをコンパイルするとアドレスに食い違いができて落ちる事もあった)。charはcharとしてスタックに積むはずだよ。そう(暗黙変換)なる処理系もきっとあるのでしょうが、いずれにせよコンパイラ依存ではないでしょうかね。

 ついでに言うと、標準Cライブラリのisalpha()などisxxxxx()系の関数のソースを見てみると面白い。きっと多くの処理系でASCIIを想定したビット(型は大体int)の配列になっていると思うんだけど、配列の大きさが256じゃなくて257なのよね。それで、実際に配列にアクセスする際に[charcode+1]を見る。これもEOF(=-1)が入ってくる事を想定した作りになっとるって訳。

投稿日時 - 2008-07-04 23:29:20

お礼

早速の回答ありがとうございます。

>そこから文字1つは慣習的にEOFを想定のうちに入れたintになってるんじゃないかね。
>EOFが絶対来ない様な場面でも。
ふむ。getchar() が戻り値を int にしているのは単純に EOF を受けるためだと、お気楽に認識しておりました。

C の慣習なんですかねえ。個人的には、「EOF が有り得ない」と認識しているなら、それをコードに char 型として反映して欲しいところですが。

投稿日時 - 2008-07-05 00:28:48

ANo.1

CとC++で違いますが、Cの場合は、charの値というのは書いた瞬間にintになりますので、関数の仮引数でchar型というのはあり得ません。

Cでは 'A' はint型。
C++では 'A' はchar型。

投稿日時 - 2008-07-04 22:17:17

補足

>Cの場合は、charの値というのは書いた瞬間にintになりますので
>関数の仮引数でchar型というのはあり得ません。

お手数ですが「書いた瞬間に int になる」というのをもう少し説明していただけますか?

int foo(int c)
{
 return c;
}



int foo(char c)
{
 return c;
}

からは「まったく同じ」オブジェクトが作成されるということですか?

投稿日時 - 2008-07-05 00:08:52