jackee777のブログ

情報系学生のつぶやき

why scanf("%s", string) is the best grammer?

なぜ,scanf("%s", string) であり,scanf("%s", &string) ではないのか

 

プログラム初心者の時には,scanf における & の時はおまじないであったから
何にも気にしていなかったけれど,気になったので振り返るとする.

 

なぜ振り返るのか...
その理由は簡単で,ぶっちゃけ scanf("%s", string) でも scanf("%s", &string) でも
動作は変わらない(ほとんどの場合).

にもかかわらず,文法として正しいのは scanf("%s", string) である.

どうして,& をつけても正しく動くのか,なぜ & を付けるのが正解なのかを考えていきましょう.動作が同じなら,それで良くない?かと思うかもですが,どうせなら正しい文法を書きましょう!

 

基本的に,scanf は次のように紹介されています.

www.tutorialspoint.com

#include <stdio.h>

int main () {
   char str1[20], str2[30];

   printf("Enter name: ");
   scanf("%s", str1);

   printf("Enter your website name: ");
   scanf("%s", str2);

   printf("Entered Name: %s\n", str1);
   printf("Entered Website:%s", str2);
   
   return(0);
}

 &string でなく string が使われていることがわかりますね.
他のサイトでも同じような例文が見られるかと思います.

 

アドレスと配列の関係がわかっていると,配列の先頭のアドレスが返されるから,そりゃそうよって思いました.でも,&string でも動いちゃうなら,普段と同じように & 着けちゃった方が楽だし,なんで & つけても動いちゃうか知りたい,と思って調べました.

そして,物凄く同意できたのは,この解答.

stackoverflow.com

An array "decays" into a pointer to its first element, so scanf("%s", string) is equivalent to scanf("%s", &string[0]). On the other hand, scanf("%s", &string) passes a pointer-to-char[256], but it points to the same place.
Then scanf, when processing the tail of its argument list, will try to pull out a char *. That's the Right Thing when you've passed in string or &string[0], but when you've passed in &stringyou're depending on something that the language standard doesn't guarantee, namely that the pointers &string and &string[0] -- pointers to objects of different types and sizes that start at the same place -- are represented the same way.
I don't believe I've ever encountered a system on which that doesn't work, and in practice you're probably safe. None the less, it's wrong, and it could fail on some platforms. (Hypothetical example: a "debugging" implementation that includes type information with every pointer. I think the C implementation on the Symbolics "Lisp Machines" did something like this.)

 

配列 string は最初の要素のポインタを返すので,string と &string[0] は同一と言える.一方で,&string は配列全体のポインタを返すので,意味合いとしては異なるが
同じポインタを指す.

(同じことは wikipedia の english page には書いてある.日本語版には,現時点ではないので誰か編集して欲しいところ.なぜか buffer overrun など細かい所は,日本語ページの方がずっと詳しい)

scanf format string - Wikipedia

scanf - Wikipedia

 

ここからは省略するが

結局,c 言語の規格によるもので,&string の場合は常に正しい動作をするとは保証できない.
でも,&string って書いて動作しなかった例に遭遇したことはない.

って言ってます.これは割とその通りだと思いました.

 

アドレスはこんな感じで確認できます.
visual studio 2010-2015 , MinGW, cygwin で動作確認(多分 linux 系でも変わらん)

#include <stdio.h>

int main (void) {
   char string[30];

printf("%p %p %p", string, &string[0], &string) return(0); }

 

一応,c の規格はこんな感じで(C99, C11, C18 等),

C (programming language) - Wikipedia

実際には windowslinux 系でもちょっと違う(posix とか).
visual studio なら,製品の年代によって違います.
(これは C++ のですけど...)

Visual C++ Language Conformance | Microsoft Docs

 

 余談

ここからは調べてないんですけど,多分アドレスの扱いなんて物凄く古い規格で決めてると思いますし,& のあるなしで脆弱性が簡単に生まれてしまうなんてのは避けたいはずなので,& があってもなくても同じアドレスになるのは普通な気がしました.

(&&string にすると,visual studio ではエラーとなるので,間違ったメモリアクセスは基本起きないとわかります.)

 

追記

一応,qiita の記事にもありました.

アドレス部分の話をちゃんとしてくれてます.ただ,& を付けても動く理由についてはあまり言及する人はいない傾向です.

こちらのコメントにありますが,別に char 型でなくても良いです.int 型の配列でも同じことが言えます.

qiita.com