Help us understand the problem. What is going on with this article?

C++の変数を理解しよう

More than 1 year has passed since last update.

(5/15: 明らかに意味の通っていない場所・全く記述が足りていない場所・規格書のURLの誤りを修正)
(5/17: 前半を大幅に加筆修正(と書くしかないくらい加筆修正した))

はじめに

プログラミング初心者たちが躓きやすい点のひとつとして「変数」があります。よくある説明としては「変数とは箱のようなものである」というものがあるようです。色々な意見があると思いますが、初心者のうちは良いでしょう。しかし、本職のプログラマがこのような曖昧な理解をしていてはいけません。C++プログラマならば変数とは何であるかきちんと説明できてしかるべきです。

ではきちんとした説明とは何でしょうか。
言うまでもありません。
言語仕様書です。
言語仕様書を引くのです。
言語仕様書がプログラミング言語を定義しています。
言語仕様書こそがプログラミング言語をプログラミング言語たらしめているといっても過言ではありません。
これを引いて説明できてこそC++プログラマと言えるでしょう。

さあ、C++言語仕様書を読みましょう。「変数とは箱のようなものである」などという甘ったれた理解から抜けだしましょう。言語仕様書が唯一にして無二なる涅槃への道なのです。涅槃とは、すなわち、C++完全理解。さあ、C++完全理解への第一歩を踏み出しましょう。

まずは、変数です。

はじめに(2)

冗談はさておきまして、本記事では言語仕様書を参照しながら「変数とは何ぞや」というあたりを追いかけてみたいと思います。言語仕様書は open-std.org さんで手に入る最新の C++17 標準の draft 版、 N4659 を使います。URLはhttp://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdfです。本当は publish されたものを購入して読むのが本筋なのでしょうが、結構高いですし、本記事は正式な書き物でもないので、これでいいでしょう。

用語について

おおむね

  • 言語仕様書上で意味が定義されている語は英語のまま
  • 通常の英単語の意味で使っている語は和訳

という方向性です。表記揺れがでるのもなんですし、原文を参照する記事でもありますので、用語は英語のままが便利だと思います。しかしながらC++用語か自然言語の意味を用いているのか微妙なものもあり、私の感で分けてしまっているものもあります。

調べる対象のコード

とりあえずコードです。
変数を全て理解するのも大変なので、今回は対象をぐっと絞って次のコードだけを説明します。

#include<iostream>
int foo;
int main() {
  foo = 57;
  std::cout << foo;
  return 0;
}

57が表示されるだけのコードです。
foo = 57;foo57 が代入され、 std::cout << foo; で表示されます。

本記事ではこのコード中で変数っぽい foo について追いかけます。std::cout も変数っぽいですが、本記事ではあくまで foo について追いかけます。

変数とは何か

早速ですが仕様書を引きます。
変数を英語で言うと、普通は variable ですよね。そこで "variable" で検索しつつそれっぽい文章を探すと "6. Basic Concepts" に次のような文章がみつかります。

A variable is introduced by the declaration of a reference other than a non-static data member or of an object.
(拙訳)variable は非staticなデータメンバ以外の参照もしくは object の declaration で導入される。

とりあえずここを起点に言語仕様書を追いかけることにします。

謎の単語がふたつありますね。declaration と object です。これから続々とわからない言葉がでてきますのでメモしておきましょう。「調べたい言葉」リストはスタックで、わからない言葉が出てきたら詰み、右の言葉から順に調べていく、ということにします。

調べたい言葉
declaration object
調べた言葉
variable: object の declaration で導入されるもの

object

object を調べます。
これは "4.5 The C++ object model" に書いてあります。やはり基本的には全文を読む必要があるのですが、簡単のため、本記事に必要そうな二文だけを抜き出します。

まず一文目は

The constructs in a C++ program create, destroy, refer to, access, and manipulate objects.
(拙訳) C++ program の構成物は object を create し、 destroy し、 refer し、 access し、 manipulate する。

です。

要するに「objectというのはC++プログラムの操作の対象ですよ」と言っています。

普段C++プログラマが何気なく操作しているあれやこれやには object という名前がついています。念のため書いておきますが、いわゆるオブジェクト指向のオブジェクトとは違います。C++ の仕様書の中では object というのは独自に定義された意味を持っています。(余談: 本記事ではあまり深追いはしませんが、この "object" なる用語はC/C++言語仕様書のあちこちに出てくるので、C/C++言語仕様書リーディングをしたい方は確実に抑えておきましょう。)

二文目は

An object occupies a region of storage in its period of construction (15.7), throughout its lifetime (6.8), and in its period of destruction (15.7).
(拙訳) object は construction の期間と、 lifetime の間と、 destruction の期間に、storage の領域を占有する。

です。要するに

  • object というのは storage の一部
  • object が storage を占有するのは constructor が呼ばれる直前から destructor から帰る直後まで

と言っています。

storage という新しい言葉が出てきましたね。これを調べましょう(construction/lifetime/destructionは今回は無視します)。

調べたい言葉
declaration object storage
調べた言葉
variable: object の declaration で導入されるもの

storage

storage の説明は「4.4 The C++ memory model」に書いてあります。
これも二文だけ抜き出すと、

The fundamental storage unit in the C++ memory model is the byte. A byte is at least large enough to contain any member of the basic execution character set (5.3) and the eight-bit code units of the Unicode UTF-8 encoding form and is composed of a contiguous sequence of bits, the number of which is implementation-defined.
(拙訳)C++ memory model の基本的な storage の単位は byte である。byte は少なくとも basic execution character set と Unicode UTF-8 のエンコーディングである8bitコード単位を保持できる連続したbitの列で、その数は implementation-defined である。

要するに

  • storage は byte の集合
  • byte というのは 8bit の情報を保持できるもの

と言っています。
普段我々が byte という語を使うときは単に 8bit のことを指すことが多いと思いますが、C++の規格書上では「(少なくとも)8bit分の情報を『保持できる』何か」であるという点に注意しましょう。

調べたい言葉
declaration object
調べた言葉
variable: object の declaration で導入されるもの
storage : byte の集合
byte    : (少なくとも)8bitの情報を保持できるもの

object(2)

話を object に戻しましょう。

  • object というのは storage の一部
  • object が storage を占有するのは constructor が呼ばれる直前から destructor から帰る直後まで

でした。

storage の意味がわかったので object の意味もわかりましたね。

調べたい言葉
declaration
調べた言葉
variable: object の declaration で導入されるもの
object  : storage の一部を占有するもの
storage : byte の集合
byte    : (少なくとも)8bitの情報を保持できるもの

declaration

では declaration を調べましょう。declaration の説明は "6.1 Declarations and definitions" に載っています。基本全文を読む必要があるのですが、簡単のため、一文だけを取り出します。

A declaration (Clause 10) may introduce one or more names into a translation unit or redeclare names introduced by previous declarations.
(拙訳) declaration は translation unit に一個以上の name を導入したり、先行する declaration により導入された name を redeclare したりする。

あたらしい概念がふたつ出てきてしまいました。translation unit と name です。これらを調べてみましょう。

調べたい言葉
declaration name translation_unit
調べた言葉
variable: object の declaration で導入されるもの
object  : storage の一部を占有するもの
storage : byte の集合
byte    : (少なくとも)8bitの情報を保持できるもの

translation unit

translation unit の定義は "5.1 Separate translation" に書いてあります。

The text of the program is kept in units called source files in this International Standard. A source file together with all the headers (20.5.1.2) and source files included (19.2) via the preprocessing directive #include, less any source lines skipped by any of the conditional inclusion (19.1) preprocessing directives, is called a translation unit.
(拙訳)プログラムを構成する文字列は、本国際標準の中で source file と呼ばれる単位に記録される。preprocessing directive #include ですべての header と source file をひとつにまとめ、そこからいくらかの source line が conditional inclusion という preprocessing directive でスキップされた source file は translation unit と呼ばれる。

ここはプリプロセッサが絡む話で、先に進もうとすると闇が広がっているので、詳細は省きます。要するにプリプロセッサを通した後のソースコードひとつを translation unit と呼びますよ、と言っています。

調べたい言葉
declaration name
調べた言葉
variable         : object の declaration で導入されるもの
object           : storage の一部を占有するもの
storage          : byte の集合
byte             : (少なくとも)8bitの情報を保持できるもの
translation unit : プリプロセッサを通したソースコード丸ごとひとつ

name

name の定義は "6. Basic Concepts" にあります。

A name is a use of an identifier(5.10), operator-function-id(16.5), literal-operator-id(16.5.8), conversion-function-id(15.3.2), or template-id(17.2) that denotes an entity or label (9.6.4, 9.1)
(拙訳)name は entity もしくは label を表す identifier, operator-function-id, literal-operator-id, conversion-function-id, もしくは template-id の使用である。

今回の対象に合わせてばっさり削りますと、「name は entity を表す identifier の使用である」として良いと思います。

entity, identifier と調べなければならないものが増えました。

調べたい言葉
declaration name entity identifier
調べた言葉
variable         : object の declaration で導入されるもの
object           : storage の一部を占有するもの
storage          : byte の集合
byte             : (少なくとも)8bitの情報を保持できるもの
translation unit : プリプロセッサを通したソースコード丸ごとひとつ

identifier

identifier の定義は "5.10 Identifiers" に書いてあります。

An identifier is an arbitrarily long sequence of letters and digits.
(拙訳)identifierは任意の長さの文字や数字の列である。

この後の細かい部分は省きますが、よくあるプログラムの範囲内であれば、正規表現的の [a-zA-Z_][a-zA-Z_0-9]* が accept する文字の並びを identifier と考えて良いでしょう。x とか hoge とか abc123 とかですね。今回調べる対象にしているプログラムの中の foo も identifier です。

調べたい言葉
declaration name entity
調べた言葉
variable         : object の declaration で導入されるもの
object           : storage の一部を占有するもの
storage          : byte の集合
byte             : (少なくとも)8bitの情報を保持できるもの
translation unit : プリプロセッサを通したソースコード丸ごとひとつ
identifier       : [a-zA-Z_][a-zA-Z_0-9]+

entity

entity の定義は "6. Basic Concepts" にあります。

An entity is a value, object, reference, function, enumerator, type, class member, bit-field, template, template specialization, namespace, or parameter pack.
(拙訳)entity とは value, object, reference, function, enumerator, type, class number, bit-field, template, template specialization, namespace, あるいは parameter pack である。

色々列挙されていますが、今回追いかけたい範囲内においては、「色々あるが object は entity の一種である」ぐらいの理解で良いのではないでしょうか。

調べたい言葉
declaration name
調べた言葉
variable         : object の declaration で導入されるもの
object           : storage の一部を占有するもの
storage          : byte の集合
byte             : (少なくとも)8bitの情報を保持できるもの
translation unit : プリプロセッサを通したソースコード丸ごとひとつ
identifier       : [a-zA-Z_][a-zA-Z_0-9]+
entity           : 色々あるが object は entity の一種

name(2)

さて、nameの話に戻りましょう。「name は entity を表す identifier の使用である」でした。意味が通るようになりましたね。調べた言葉リストに追加しましょう。

調べたい言葉
declaration
調べた言葉
variable         : object の declaration で導入されるもの
object           : storage の一部を占有するもの
storage          : byte の集合
byte             : (少なくとも)8bitの情報を保持できるもの
translation unit : プリプロセッサを通したソースコード丸ごとひとつ
identifier       : [a-zA-Z_][a-zA-Z_0-9]+
entity           : 色々あるが object は entity の一種
name             : entity を表す identifier

declaration(2)

declaration に戻ってきました。

Declaration の定義は "10 Declarations" に載っています。

Declarations generally specify how names are to be interpreted.
(拙訳)declaration は通常 name がどのように解釈されるか指定する。

調べたい言葉
調べた言葉
variable         : object の declaration で導入されるもの
object           : storage の一部を占有するもの
storage          : byte の集合
byte             : (少なくとも)8bitの情報を保持できるもの
translation unit : プリプロセッサを通したソースコード丸ごとひとつ
identifier       : [a-zA-Z_][a-zA-Z_0-9]+
entity           : 色々あるが object は entity の一種
name             : entity を表す identifier
declaration      : name がどのように解釈されるか指定するもの

はい、調べたい言葉がとりあずなくなりました。

variable というのは object の declaration で導入されるもので、declaration は name がどのように解釈されるか指定するものです。name は entity(ここではobject)に解釈されます。

さて、これらがどのように繋がるか見ていきましょう。

"10 Declarations" に

Declarations have the form (以下略)

に続いて構文上の規則が書かれていて、この中でそれぞれの構文がどのような意味を持つか述べられています。規則は BNF というやつで書かれていて、これについて書くと大変なことになるので、 BNF 自体の読み方は省略します。

さて、 int foo; を題材に追いかけていきましょう。

int foo;declaration です。declaration にはいくつか種類があるのですが、int foo;block-declaration です(天下り的な説明ですが、かなり先読みしないとわからないのでこのように読んでいくことにします)。

block-declaration にもいくつか種類があるのですが、int foo;simple-declaration です。

この simple-declaration にもいくつか種類があるのですが、int foo;decl-specifier-seq init-declarator-listopt ; です。

ここで int foo; は3つの部分に分解されます。
intdecl-specifier-seq で、
fooinit-declarator-list で、
; はこの規則の末尾の ; です。

ここで p156 の 10 を読むと次のような文章があります。

If the decl-specifier-seq contains no typedef specifier, the declaration is called a function declaration if the type associated with the name is a function type (11.3.5) and an object declaration otherwise.
(拙訳)もし decl-specifier-seqtypedef specifier を含まない場合、もしその name に associate された型が function type(11.3.5)であれば、その declaration は function declaration と呼ばれ、そうでなければ object declaration と呼ばれます。

inttypedef は含まれていませんので、int foo; は object declaration であるということになります。

さて、int の部分を読み進めましょう。intdecl-specifier-seq でした。この decl-specifier-seq の定義は "10.1 Specifiers" に decl-specifier の並びとして定義されています。decl-type-specifier にはいくつか種類があるのですが、
intdefining-type-specifier だけです。

defining-type-specifier の定義は "10.1.7 Type specifiers" に書かれています。ここを見ると defining-type-specifier にもいくつか種類があることがわかるのですが、inttype-specifier です。

この type-specifier の定義は "10.1.7 Type specifiers" に書かれています。ここを見ると type-specifier にもいくつか種類があることがわかるのですが、intsimple-type-specifier です。

この simple-type-specifier の定義は "10.1.7.2 Simple type specifiers" に書かれています。ここを見ると simple-type-specifier にもいくつか種類があることがわかります。この中に int が含まれています。

というわけでこれを逆順に辿ると、intdecl-specifier-seq であると言えることになります。

次は foo です。fooinit-declarator-list でした。init-declarator-list は "11 Declarators" で定義されています。init-declarator-list はカンマ区切りの init-declarator の並びなのですが、foo にはカンマが含まれていませんので、fooinit-declarator です。

init-declarator の定義は declarator initializeropt です。initializer は初期化式を書くところで、int foo; にはありませんので、foodeclarator であるということになります。

declarator にもいくつか種類がありますが、fooptr-declarator です。

ptr-declarator にもいくつか種類がありますが、foonoptr-declarator です。

noptr-declarator にもいくつか種類がありますが、foodeclarator-id attribute-specifier-seqopt です。また、ここで attribute-specifier-seq は空ですので、foodeclarator-id である、ということになります。

declarator-id の定義は ...opt id-expressiond ですが、foo の中には ... はありませんので、fooid-expression である、ということになります。

id-expression の定義は "8.1.4 Names" に書いてあります。id-expression にもいくつか種類がありますが、foounqualified-id です。

unqualified-id の定義は "8.1.4.1 Unqualified names" にあります。unqualified-id にもいくつか種類がありますが、fooidentifier です。

identifier の定義は "5.10 Identifiers" に書かれています。さすがにもうええやろという感じなので雑に説明しますが、いわゆる変数名になるやつが identifier です。(余談: C++17では identifieruniversal-character-name という ASCII 以外の文字を使えるようになっています。これ、 Unicode ではなく ISO/IEC 10646 という Unicode と概ね互換ないやらしい定義になっており…。)

さて、これまでつらつらと構文規則について述べてきました。ここまでを把握していると意味規則の方についての文章を読めるようになります。さっそく "11.3 Meaning of declarators" から引いてみましょう。

Thus, a declaration of a particular identifier has the form
  T D
where T is of the form attribute-specifier-seqopt decl-specifier-seq and D is a declarator. Following is a recursive procedure for determining the type specified for the contained
declarator-id by such a declaration.
(拙訳)従って、特定の identifier の declaration は
  T D
の形をしていて、Tattribute-specifier-seqopt decl-specifier-seq の形をしていて、D は declarator である。以下はこのようなdeclaration での、 declarator-id にどのような type が指定されるかを決めるための再帰的な手続きである。

First, the decl-specifier-seq determines a type. In a declaration T D the decl-specifier-seq T determines the type T.
(拙訳)まず、decl-specifier-seq は type を決める。declaration T D の中でdecl-specifier-seq T はその type を T に決める。

In a declaration attribute-specifier-seqopt T D where D is an unadorned identifier the type of this identifier is "T"
(拙訳)Dが単なる identifier であるような declaration attribute-specifier-seqopt T D では、その identifier の type は "T" である。

int foo の場合 Tint で、 Dfoo です。
さあ、ようやく int foo; の意味がわかりました。

variable の name である foo の type は int なのです!!!

variable

では再度 "6. Basic Concepts" に戻りましょう。

variable の最初の一文はこんなでした(再掲)。

A variable is introduced by the declaration of a reference other than a non-static data member or of an object.
(拙訳)variable は非staticなデータメンバ以外の参照もしくは object の declaration で導入される。

こんな一文があります。

The variable’s name, if any, denotes the reference or object.
(拙訳)その variable の名前は、もしあれば、参照か object を表す。

foo は参照ではありませんので、 object を表します。
注意しなければならないのは次の二点です。

  • foo は variable の名前である
  • foo は object を表す

違いに注意しましょう。foo は object そのものではなく、object を表す「variable の name」です。

中間まとめ

中間まとめです。

  1. int foo; で variable が導入される。
  2. foo の type は int である。
  3. foo は variable の name である。
  4. fooidentifier である。
  5. foo は object を表す。
  6. object は storage の一部である
  7. storage は byte から構成されている
  8. byte は(少なくとも)8bitを保持できるものである
  9. object はプログラムの読み書きの対象である

結局 foo は「何らかの情報を保持できるものを表す名前」なのです。

statement & expression

さて、「変数が何か」はとりあえずわかりました。
「変数はどう動くか」を見ましょう。
変数が何かしら動作するのは statement や expression の中です。ですので仕様書のこのへんを読みましょう。

変数の記事なので main の定義や return 0; は無視して、foo = 57;std::cout << foo; を追いかけます。

statement

まず軽いところから。「foo = 57; の次に std::cout << foo; が実行される」と知っている方も多いと思います。これは規格書上では "9 Statements" の冒頭に書いてあります。

Except as indicated, statements are executed in sequence.
(拙訳)特に言及することがない限り、statementは順に実行される。

というわけで値が代入される前に表示されてしまうことを心配する必要はありません。

foo = 57;std::cout << foo; を個別にみていきましょう。どちらも expression statement と呼ばれる statement です。expression statement の説明は "9.2 Expression statement" にあります。expression statement の定義は「expression の後にセミコロン(;)が付いたもの」です(ちなみに expression なしで単独の ; だけでも expression statement です)。というわけで expression foo = 57std::cout << foo を追いかければいいわけですね。

expression

expression の定義は "8.19 Comma operator" に書かれています。expressionassignment-expression か、それをコンマ演算子で並べたものです。コンマ演算子は今回使っていないので、foo = 57std::cout << fooassignment-expression です。

assignment-expression の定義は"8.18 Assignment and compound assignment operators" に書かれています。assignment-expression は色々あるのですが、std::cout << fooconditional-expression で、foo = 57logical-or-expression assignment-operator initializer-clause です。foo = 57 を分解すると、
foological-or-expression で、
=assignment-operator で、
57initializer-clause です。

さて、 foo = 57 の意味ですが、これは自然言語で書かれています。

In simple assignment (=), the value of the expression replaces that of the object referred to by the left operand.
(拙訳) simple assingment (=) では、左側の operand で refer される object をその値で置き換える。

(すみません、ここ、the value of the expression が何を指してるか読み取れませんでした。right operand の value であることは間違いないのですが、どこに書かれているのか見つけられませんでした。どなたかご存知でしたら教えてください。)

initializer-clause の定義は "11.6 Initializers" に書かれています。initializer-clauseassignment-expression もしくは波括弧リストなのですが、foo = 57 では波括弧は使っていないので、 57 もまた assinment-expression です。57assignment-expression ですので、結局これは conditional-expression です。

conditional-expression の定義は "8.16 Conditional operator" に書かれています。conditional-expressionlogical-or-expression もしくはいわゆる三項演算子の expression です。

というわけで結局 foo, 57, std::cout の3つすべてが logical-or-expression ということがわかりました。

ここから同じような感じで長々と続きます。長いので追いかける構文規則名だけを順にならべますと、
logical-or-expressionlogical-and-expressioninclusive-or-expressionexclusive-or-expressionand-expressionequality-expressionrelational-expressionshift-expressionadditive-expressionmultiplicative-expressionpm-expressioncast-expressionunary-expressionpostfix-expressionprimary-expression
となります。

primary-expression は何種類かあるのですが、
foo はここから primary-expressionid-expressionunqualified-ididentifier となり、
std::coutprimary-expresionid-expressionqualified-id となり、
57literal になります。

literal の定義は "5.13 Literals" に書かれています。長いので省略しますが 57 の型 int で、その value は文字列を10進数で解釈したものであると読み取れます。

unqualified-id の定義は "8.1.4.1 Unqualified names" に書かれています。unqualified-id にも何種類かあるのですが、fooの場合はidentifierです。これに対する意味は自然言語で次のように書かれています。

An identifier is an id-expression provided it has been suitably declared (Clause 10).(中略) The type of the expression is the type of the identifier. The result is the entity denoted by the identifier.
(拙訳)identifierは適切に declare された id-expression である。(中略)この expression の type は identifier の type である。result はこの identifier の表す entity である。

fooint foo のところで適切に declare されていますので、この文章を適用できます。つまり expression の中の foo の型は int で、 expression の result は foo の表す entity、すなわち宣言のところで導入された variable です。

foo = 57 の意味

= の左側の foo は「int foo; の宣言で導入された variable(すなわちobject)」を表しています。= の右側の 57 は 57 という int を表しており、その value は 57 でした。

"8.18 Assignment and compound assignment operators" の記述に戻りますと、= は左側の operand の表す object の value を右側の operand の表す値で置き換えるという意味でした。というわけで

foo = 57 は「int foo; の declaration で導入された variable(すなわちobject)の value を 57 で置き換える」という意味になります。

std::cout << foo の意味

qualified-idstd::cout << は本記事の範囲を超えそうなので省略させてください。とりあえず << の右側の値を表示するという意味です。

<< の右側は foo です。fooは「int foo; の declaration で導入された variable(すなわちobject)」を表しています。この object の value は 57 でしたので、この式を評価すると 57 が表示されます。(なぜこの object の value が 57 であると言えるかというと、"9 Statements"に書いてあった通り、 statement は順に execute されるからです。)

まとめ

非常に雑な説明かつ省略しまくりになってしまいましたが、変数の基本的なところは抑えられたかと思います。

さあ、まとめます。

言語仕様書から
int foo;foo と言う名前の variable の一種である object が導入され、
foo = 57int foo; で導入された object の値を 57 に置き換え、
cout << fooint foo; で導入された object の値を読み取ってから使っている」
ということが読み取れました!

さいごに

疲れた

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away