ゼロ番地を先頭とするページにマップする

mmapのflags引数にMAP_FIXEDフラグを指定することで、OSの実装によってはゼロ番地を先頭とするページにマップできます*1

たとえば、i386以降のCPU, Vine Linux 4.0, gcc-4.1.1を使った環境で以下のコードをコンパイルして実行するとゼロ番地にアクセスできます。

#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>

int main(){
  int* p = (int*)mmap(0, getpagesize(), PROT_READ | PROT_WRITE, 
                     MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, 0, 0);
  *p = 1;

  printf("*(int*)%d = %d\n", (int)p, *(int*)0);
  return 0;
}

実行すると以下のようになります。

$ gcc main.c
$ ./a.out
*(int*)0 = 1
$

以下、注意点。



Cの仕様上では、start引数に0を指定しても数値の0を指すとは限りません。なぜなら、start引数の型はvoid*であり、Cの仕様ではポインタを書くべきところに書かれた0は、コンパイル時にヌル・ポインタに変換されることが保証されているからです*2コンパイラがポインタ型の式だと判断できる場合のみ)。

なお、C++でもほとんど同様のようです*3。自分には、CとC++の違いは、C言語の場合、#define NULL (void*)0 か #define NULL 0 どちらでもありうるが、C++だと #define NULL (void*)0 がありえないということしかわかりませんでした。

よって、ハードウェア, OS, C/C++コンパイラによっては、start引数で渡す値がコンパイル時に0ではない値に変換されているかもしれないので、ソースコードでstart引数に0を指定したからといって、実行時にmmapでマップされたページが必ずしもゼロ番地ではないかも知れないことに注意です。


以上、何の役に立つかわからないけど面白かったのでメモっときます。

ちなみにWindowsの場合、ゼロ番地から64KB分*4のアドレスをユーザプログラムのプログラマがVirtualAlloc APIで確保(予約)することはできません。これは、ヌル・ポインタが指すアドレス先に値を代入しようとしたときに、必ずメモリアクセス違反が発生するようにするために、意図的にそう決まっています*5

ということは、Windowsが動くような環境において、ヌル・ポインタが0と一致する(させる)のは当然だとMicrosoftは考えているのでしょうね。

*1:SUSv3では、「MAP_FIXEDフラグを指定した場合、start引数はマップされるページのヒントとなるアドレスではなく、マップさせたいページのアドレスを意味するようになり、マップに失敗した場合はエラーを返す」としか書いてありません。よって、startに0を指定した場合は必ず失敗するようにmmapを実装しても仕様を満たします。

*2:http://www.kouno.jp/home/c_faq/c5.html 5.2項

*3:注解 C++ リファレンスマニュアル 4.6: ISBN 4901280392 といっても、これ古い仕様みたいですけど・・・

*4:Windows2000の場合。Windows98だと4KB分

*5:Advanced Windows 13.2 :ISBN 4-7561-3805-5