constとメモリ構造の関係性

今日は会社でコードレビュー(僕が書いたコードではない)があったのだが、その中であった指摘にconstを使うと読み出し専用領域に置かれるけど別にconstつけずにスタックに置いてもいいんじゃないか?という話があった。

ちなみにコードは下記のような感じ。

#include <iostream>
#include <cstring>

class  string_container {
  char *str;

public:
  string_container(const char *arg){
    str = strdup(arg);
  }
  ~string_container(){
    delete str;
  }

  operator const char* (){
    return str;
  }
};

int
main(void){
  string_container obj("test");

  // この下の行が焦点
  const char *str2 = static_cast<const char *>(obj);
  std::cout << str2 << std::endl;

  return 0;
}

ここで単純に「const char str2」=> 「char str2」と変換をすると、コンパイルエラーとなるが、const_cast(もしくはC形式のキャスト)を使って「char *str2」に代入することは可能である。

その場合、オブジェクト内部の変数をを外部から変更できるようになってしまうのはあまり良くないですよね、という話はなんとか話せたかなとは思うのだけど、メモリ内部構造については曖昧な点があり、微妙なことを言ってしまった気がするのでちゃんと調べてみた。

さて、一般的な話として、const修飾子をつけて宣言された変数は読み込み専用領域に置かれる可能性があるということは事実であり、また実際に置かれるかどうかはコンパイラ実装依存となる。

C言語の仕様を確認したところ、read onlyなメモリ上に置かれる可能性があるということは明記されているし、C++の仕様では見つけ出すことができなかったが、Bjarne Stroustrupの「プログラミング言語 C++」ではconstな配列を読み出し専用領域に置くことで高速化する実装があるかもしれないと述べられている。

ここで一度constについて復習しよう。

/* Example A */

// 下記の宣言をした時は文字列(実体)の変更が不可
const char *str = "Hello, World!";

// NG compile error 文字列自体は変更できない
str[3] = 'c';
// OK strポインタが指すもの自体は変更可能
str = "Good evening!"; 

/* Example B */
char s[] = "Hello";
// 下記の宣言をした場合はポインタが変更不可
char* const cp = s;

cp[3] = 'a'; // OK  ポインタcpがさす実体は変更可能
cp = str;    // NG ポインタ自体は変更不可能

Example Aでは"Hello! World"という文字列が、読み出し可能領域に置かれる可能性がある。

だが、あくまでExample Aのstrというポインタ自身はただのローカル変数であることは注意が必要である。それではどういう場合に読み出し専用領域に置かれる可能性があるか?ということをもうちょっと明確にしたいと思い調べてみたところ、「注釈C++リファレンスマニュアル」にてわかりやすい例とともに書かれていたのでその箇所を引用したいと思う。

変数を読み出し専用メモリに置くことができるのは、
オブジェクトがコンパイル時に決定できる値で初期化される場合だけである。

const one = 1; //読み出し専用メモリに置ける
void f( int i ){
   const two = i; //読み出し専用メモリに置けない
}

読んだ後にそりゃ、そうだ、読み出し専用の定数はコンパイルされたプログラムコードとともにオブジェクトファイルに配置されるんだから、コンパイル時にわかっていないとそういった区別もできないということに思い至った。

ということで、一番最初のコードレビューで使われたコードの場合、実行時にしか全ての値が決まらないので、読み出し専用領域に置ける変数がないということが確認できた。

ただ、そういうことを考えなくても、constつけられる箇所は可能な限りつけるべきだという方針が「Effective C++」「Exceptiol C++」で説明されている。 constperlの「use strict;」なんかと同じようにつけておくことで「自分の足を撃つ」ことがないようにできるC++の機能の一つなので、みんなconst使おう!

参考文献:

「注釈C++リファレンスマニュアル」
「Effective C++
「Exceptional C++
参考URL:
// しまった。(別の自分のブログからの)転載のため、口調が統一されてないけど、まぁいいや。。。
追記) 後で気づいたんですが、最初のコードはstatic_cast<const char *>()も入りませんね。
暗黙的な型変換がされるはず。
つけておいた方がわかりやすくていいのかもしれませんが。