ようこそゲストさん

CPA-LABテクニカル

2008/02/23(土) issetを正しく理解する。null値とunsetの違い-PHP変数管理

nullほど人を惑わすものはないだろう。設定されてない値ってなんだ?
unsetされた変数とは違うの?
様々な疑問があろう。
ここで、明確な理解をしておきたい。(PHPの場合)

なお、ここで以下の質問に理由とともに答えられる人は読む必要のない記事です。
   ! isset ( $a )  と is_null ( $a ) 等価である。 × or ○

シンボルテーブルのおさらい

以前に変数管理の基本として書いたけれども、PHPは、シンボルテーブルという変数名と値を結びつける、管理表を内部に持っている。
変数名格納場所
a#0001番地
のような。

$a = null;

シンボルテーブル a → #0001番地 → nullという値
となる。

unset ( $a );

シンボルテーブル に a がない!
という状態になる。


$a='';
は、空文字文字の代入なので、PHPとしては当然に値を持っているものである。
シンボルテーブル a → #0001番地 → \0 という値

なお、null 値とは、 null 型 は、ある変数が値を持たないことを表す値、という若干矛盾する表現でしか表現できない特殊な値&型であるが、私見では、
null という値を持つ特殊な定数(変数)、
と理解するのがよろしいかと思う。

内部構造を知る

PHPでは、変数管理のためのシンボルテーブルという仕組みをもっている。しかし、 null を理解するためには、さらに奥深くまで知っておいたほうがいいだろう*1


PHPが作られているC言語では、変数の実メモリ領域を示す、ポインタというものが使われる。ポインタは、その格納メモリ番地を示す変数である。
Cのコンパイラが、変数名 b の メモリは、#2001と決めれば、 b = 1 としたときに、#2001番地に 1 と書き込まれる。
また、Cでは、基本的には文字列という概念はなく*2、文字列は文字コードの集合体である配列を使って表現され、文字列の最後、すなわち、配列の最後には、終端文字として「0]が代入されている*3

unset , null , = 0 , = ''

では、ここで、 unset,null =0 ='' の違いをみてみよう。

PHPでの
シンボルテーブル
C言語の中身
変数名格納場所
(zval)
内部C言語での変数名Cでのポインタ値*4 メモリの値 
$a = null ;a#0001番地nullsymbol[1]*5 0
(意味のない
メモリ領域を
指している) 
不定*6 
$a = 0;a#0001番地symbol[1]#20001
(確保された
実メモリ領域)
0
$a = '' ;a#0001番地osymbol[1]#20001
(確保された
実メモリ領域) *7
0
(Cの文字列終端コード)
unset( $a ) ;-

Cの内部構造は簡略化しているが、PHP理解のためにはこのぐらいで大丈夫のはず。


さて、まず一番下の unset をみてみましょう。
シンボルテーブルに名前がない状態。無です。これはわかりやすいですよね。


では、 = null は?
シンボルテーブルには名前があり、値が入っていることを表しています。
しかし、C言語レベルのポインタをみると、ポインタが ゼロ に設定されています。
メモリ番地ゼロの値を読み出したとしても、それはどんな値かはわかりません。したがって、Cでは、ポインタがゼロを指している時に特別な null として取り扱います。それをPHPでも承継してnull値とよんでいるわけです。


では、 = '' は?
これもシンボルテーブルにセットされ、最後のCでもポインタがセットされ、意味のある番地を示し、そこには、「ゼロ」が書き込まれています。ゼロは文字列型の終端文字です。
これは、 = 0 も同じです。
型が違うということは認識されるのですが、 機械語レベルでみると、どちらも ゼロ という同じ値です。


こちらもご参考まで。unset と =null の違い

isset 関数の働き

PHPのマニュアルには、以下のように記載されている。
isset ? 変数がセットされているかどうかを検査する
関数リファレンス -isset
これは、若干、誤解を生む表現かと思う。
正しくは、こうだ。
isset は 当該変数が null 値となっているかどうかを調べる。 null 値であれば、false。それ以外の値が設定されていれば、trueを返す。
なお、 PHP はシンボルテーブルに当該変数名がない時には、内部的に null 値が設定されているものとして扱う
もっと簡単にいえば
isset は 当該変数が null であるか、 シンボルテーブルに名前がなければ、 falseを返す。それ以外はtrue;
というのが正しいかと思う。

null と unset 変数の実務上の違い

以下のコードでPHPが、noticeを返すのはどれか?
code 1
unset( $a );
 $a . = "b" ;

code 2
$a = null;
$a . = "b" ;

code 1 は notice Undefined variable*8となり、code 2は、エラーとならない*9
(これは、一番厳密にエラー判定をした場合であり、運用でエラーを出さないように設定している場合はこの限りではない)


CakePHP1.2のコードを読んでいると、 code 2 のような使い方が目に付く。
$a = ''; とせず、 $a = null ; と初期化するなんて、なかなか通な使い方ですね。

notice Undefined variable

したがって、 notice Undefined variable は、シンボルテーブルに名前がない変数を読み出そうとしたときにでるnotice であることがわかる。
isset とは、判別方法が違うのだ。

! isset と is_null は等価か?


ここで冒頭の質問に戻ろう。


null値 は、セットされていない、などと表現してはいけなくて、明確に 「null値という値」である、と理解したほうがいいと述べた。
さらに isset は、 null値 であれば、false を返す関数であると述べた。
では、 ! isset と is_null  は同じだろうか?

実は、「関数の働きとしては、同じ」である。なぜなら、 is_null も ! isset も「当該変数が null 値かどうかを調べる関数」だからだ。


しかし、その関数動作の基礎となる、変数読み出しの点で動作が異なる。

code 3
$a = null;
if( is_null ($a)) { echo "true"; } else { echo "false"; }

code 4
unset( $a );
if( is_null ($a)) { echo "true"; } else { echo "false"; }
を考えてみよう。これが、 実は、これが ! isset関数であっても is_null であっても、同じように「true]となる。
しかし、 code 4 の is_null は、
Notice : Undefined variable
というエラー(Notice)が出る。Noticeが出ても、原則としては*10プログラムは走っていくので、関数の出力としては、どちらも true である。
code4 の is_null を ! isset に置き換えた場合、Notice はでない。(結果は同じ true)
! isset 関数では、Notice を押さえ込んでしまうが、is_null は、Notice を押さえ込まずに動作するわけである。
だから、php.iniの設定などでエラー出力を押さえてしまえば、同じ動作となる*11


思うに、PHPの内部APIの標準動作では、シンボルテーブルに設定のない変数名がコールされると、Noticeを返すのだと思う。 しかし、 isset*12 関数は、それを無視する形で動作するようになっていると考えられる。

参考にさせていただいたサイト

第18話「続・文字と文字列の扱い方」
コーディング実技試験 問題と解答解説-文字列
可変長文字列の作り方
Visual C# による文字列処理入門
コンピューター:C言語講座:可変引数について
www.AsciiTable.com

*1 : といいつつ、私はC言語は扱ったことがない。間違いがあればご指摘ください。

*2 : 通常は拡張され文字列型も扱われる

*3 : 終端文字は文字コードとしては、「\0」と)表現されることが多いが、コンピュータは数値しか扱えないのだから、究極的には、値というものは、すべて整数値に置き換えられる。

*4 : このポインタが格納されている実メモリ番地と変数名のテーブルもCコンパイラが管理している

*5 : この名前はテキトーである

*6 : そのCPUのメモリ0番地がどんな値かなんて、環境によって違いますよね

*7 : 実際には、もう一段階可変長を扱うためのポインタをかましているはずだが。。。

*8 : php.initiでE_ALL設定の場合。他のエラー制御をしている場合はこれに限らない

*9 : noticeがエラーかどうかという議論はあるかも知れない。

*10 : 設定による

*11 : もちろん、エラー出力を押さえたからいいというものではなく、内部的にはエラーなのだから、そうすべきではない

*12 : とempty

1: 通りすがり 2010年02月04日(木) 午後4時38分

まず、*9にも書いてありますが、Noticeってエラーではないと思います。変な書き方だから、直しといた方がいいよってことと思っています。
$a = null;として、
isset($a) → false なので、プログラム側からは全く同じものになってしまうのではないでしょうか。
違いを主張されていますが、結局、それを利用する方法が言語的にありませんよ、という意味です。利用者からは気にする必要が無い部分ではないかと。0とfalseは違いが利用できますが、nullとunsetは違いを利用できません。Noticeエラーを見にいくなら別ですが。わたしはこう考えますが、如何でしょうか。

2: 通りすがり 2010年02月04日(木) 午後4時40分

PHPの実装者(PHPを作る人)から見れば、nullが代入されたら、テーブルから消す実装にしても、言語的にはおかしくないのではないだろうか。

3: 通りすがり 2010年03月23日(火) 午後0時45分

staticな場合、unsetだとstaticまで解除されます。nullだとstaticのまま?

4: ごんべ 2013年02月01日(金) 午後4時55分

むむ、考えるのは勝手だがNoticeはエラーです。ということで、これも、しらんかっとってんちんとんしゃん!

5: CertaiN 2013年07月05日(金) 午後9時11分

E_NOTICEの「E」は「Error」の略ですからねもちろん。

6: ほんじ 2016年09月26日(月) 午後7時56分

NULLは不定値です。
なので、変数の示すポインタの値が0であることではありません。

C言語とかPHPというより、コンピュータとしての話になるのですが、変数セット(管理テーブル登録)時は言語として初期化することを保証していないと思います。(しているのもあるかもしれません)

例えば、言語で変数領域を確保したメモリ領域は、過去に何かのプログラムで使われていて、その後解放されている領域かもしれません。
仮に、使われていない領域だったとしても、電源ON時にメモリ空間の全領域が0であることは保証されていません。
多くの場合は0であることが多いですけどね。

そこから、2ポートメモリでのアクセスができるハードウェアで構成あったり、サーバ機など、BIOSでメモリチェックを行うPCであれば、さらに0である保証などないと思います。

そのあたりが、初期化することの意味になってくるんですけどね。

ちなみに、メモリ番地=ポインタではありません。ポインタは転送先アドレスであって、絶対アドレスではありませんので。

つまり、
「*6 : そのCPUのメモリ0番地がどんな値かなんて、環境によって違いますよね」
0番地である保証はありません。
x86やx64CPUの場合、0x8000番地以下は特殊なメモリ領域になるので、OSの上に乗っかっているアプリケーションが指し示すことは不可です。(その番地をポインタを使って参照するなど)

メモリの管理テーブルでそのアドレスがNULL値というのは、SQLでDELETEした状態のような、管理テーブルのゴミでしかなく、意味のない領域だと思いますが。。。

変数定義した直後はアドレス決定していませんというのもナンセンスな話だと思います。
それじゃつかえないじゃん!ってなると思うので。

7: ほんじ 2016年10月17日(月) 午後4時55分

スレ違いだけど、思い出したので。

サーバ機などに使われるECCとかRegisteredメモリは電源ON時に初期化します。

通常のメモリビットとは別に、データの整合性チェックを行うビットが付加されているので、初期化してからでないと逆に使えません。

私たちが使うコンピュータという考え方だとあまり感覚ないですが、機械というか半導体も完全ではなく、CPUも何億回に一回は計算ミスしますし、宇宙空間だと、放射能などが原因で勝手にメモリのビットが反転します。

そんなわけで、NULL=不定値です。

NULLを変数に書き込む場合はNULLにふさわしい値がないので0を書くことになりますけどね。

1なら1で適切な理由ないし(TRUEと間違える・・・?)、2なら2で理由ないし。-1も・・・ね
だからと言って0も適当な理由ないんですが。
FALSEだし、意図しない状態遷移の可能性が一番低いからなんですかね。

NULLってどんな値と聞かれると悩むけど、NULLを書くときに0を書くことが多い。
そういうものだと思います。

不定値だからと乱数を書くCPUリソースを使うのもね。


名前:  非公開コメント   

  • TB-URL  http://www.cpa-lab.com/tech/097/tb/
  • [php]phpでNotice Errorの Undefined index等が多発してうざい場合はこれで解決 Kemworld::Diary
    phpでNotice Errorの Undefined index等が多発してうざい場合は(エラーレベル落とせいいというのは置いておいて)、 $a = null; という感じで変数を初期化しておくといいかもしれない。issetで条件分岐してる場合はやっか...
  • Notice:Undefined Index を回避するには エリベリンデラボ
    Notice:Undefined Index やら Notice:Undefined Vriable が多発しているのを見るととても切ない。これを回避するには変数を使う前にきちんと初期化する,もしくはis_nullで分岐してから処理をする。こちらを参考にさ...