[C++] 変数のメモリアドレス検証他

[M1 Mac, Big Sur 11.6.7]

関数内外の変数がメモリのどの領域に収納されるのか検証しました。

関数外変数は静的領域、関数内変数は全てスタック領域でした。最終的にはOSが決めているようです。

newやvectorは一般的にはヒープ領域と説明されているので意外な結果でした。

vectorの最大要素数は4611686018427387903個です。int は4バイトなので

4611686018427387903*4 = 1.8446744e+19バイト = 1.8446744e+19*8 ビット

これは64ビットシステム 2^64 – 1の8倍に相当します。この出力値はIntel Mac RAM32GB(M1は8GB)でも同じだったのでメモリ容量に比例しているわけでもないです。

つまり実際のハードスペックを考慮しての実装上可能値ではなく、スタックオーバーフロー対策の参考にはなりません。机上の計算を再現するだけのmax_size関数が何のために存在しているのかよく分かりません。

#include <iostream>
#include <vector>

int num0;

int main()
{
    int num1;
    int num2 = 3;

    int *ary;
    ary = new int[5];

    std::vector<int> vec = {1,2,3};
    
    std::cout << "num0 " << &num0 << std::endl;
    std::cout << "num1 " << &num1 << std::endl;
    std::cout << "num2 " << &num2 << std::endl;
    std::cout << "ary  " << &ary << std::endl;
    std::cout << "vec  " << &vec << std::endl;
    std::cout << "vec.max_size() " << vec.max_size() << std::endl;
    
}
--------------------------------------------------
出力例
--------------------------------------------------
num0 0x1048e4128
num1 0x16b5236a8
num2 0x16b5236a4
ary  0x16b523698
vec  0x16b523680
vec.max_size() 4611686018427387903

[C++] vectorの挙動検証 erase

[M1 Mac, Big Sur 11.6.7, clang 13.0.0, NO IDE]

前回、前々回と同様の検証をerase関数で実施しました。

erase関数の場合は、メモリアドレスは削除箇所から前の要素は変わらず、削除箇所から後ろは前に詰める形になります。

イテレータの再設定は必要です。再設定しないとwhile文ではデータが格納されていると認識している旧endを延々と探すことになり、ループが止まりません。

#include <iostream>
#include <vector>

using std::to_string;

int main()
{
    std::vector<int> vec{ 1, 2, 3, 4, 5};
    std::vector<int>::iterator itr = vec.begin();
    
    // vectorの各要素のアドレスを出力
    std::cout << "vector" << std::endl;
    while (itr != vec.end())
    {
        std::cout << &(*itr) << std::endl;
        itr++;
    }

	// 3番目の要素を削除
	vec.erase(vec.begin() + 2);
	
	// イテレータ再設定
	std::vector<int>::iterator itr2 = vec.begin();
	
	// 再度アドレス出力
	std::cout << "vector削除後" << std::endl;
    while (itr2 != vec.end())
    {
        std::cout << &(*itr2) << std::endl;
        itr2++;
    }

	for (int num:vec)
    {
        std::cout << to_string(num) << std::endl;
    }
}
--------------------------------------------------
出力例
--------------------------------------------------
vector
0x13e6068c0
0x13e6068c4
0x13e6068c8
0x13e6068cc
0x13e6068d0

vector削除後
0x13e6068c0
0x13e6068c4
0x13e6068c8
0x13e6068cc
1
2
4
5

[C++] vectorの挙動検証 insert

[M1 Mac, Big Sur 11.6.7, clang 13.0.0, NO IDE]

前回と同様の検証をinsert関数で実施しました。

insertやeraseのようなイテレータが戻り値の関数では、これを受け取ればイテレータを再設定せずに済む場合があります。

先頭に挿入した場合だけ再設定不要で、それ以外の場合は再設定しないと全てのメモリアドレスを確認できません。

#include <iostream>
#include <vector>

using std::to_string;

int main()
{
    std::vector<int> vec{ 1, 2, 3, 4, 5};
    std::vector<int>::iterator itr = vec.begin();
    
    // vectorの各要素のアドレスを出力
    std::cout << "vector" << std::endl;
    while (itr != vec.end())
    {
        std::cout << &(*itr) << std::endl;
        itr++;
    }

	// 要素を挿入
	itr = vec.insert(vec.begin(),6);
	
	// 再度アドレス出力
	std::cout << "vector挿入後" << std::endl;
    while (itr != vec.end())
    {
        std::cout << &(*itr) << std::endl;
        itr++;
    }

	for (int num:vec)
    {
        std::cout << to_string(num) << std::endl;
    }

}
--------------------------------------------------
出力例
--------------------------------------------------
vector
0x1336068c0
0x1336068c4
0x1336068c8
0x1336068cc
0x1336068d0

vector挿入後
0x1336068e0
0x1336068e4
0x1336068e8
0x1336068ec
0x1336068f0
0x1336068f4
6
1
2
3
4
5

[C++] vectorの挙動検証 push_back

[M1 Mac, Big Sur 11.6.7, clang 13.0.0, NO IDE]

vectorの各要素のメモリアドレスおよび要素を追加したときのアドレスの変化をチェックしました。

push_back関数で要素を追加すると、16バイト先のメモリアドレスから再度要素を配置することがわかりました。イテレータを設定していた場合はそれが使えなくなるため、再度設定が必要になります。

#include <iostream>
#include <vector>

using std::to_string;

int main()
{
    std::vector<int> vec{ 1, 2, 3, 4, 5};
    std::vector<int>::iterator itr = vec.begin();
    
    // vector各要素のアドレスを出力
    std::cout << "vector" << std::endl;
    while (itr != vec.end())
    {
        std::cout << &(*itr) << std::endl;
        itr++;
    }

	// 要素を追加
	vec.push_back(6);
	// イテレータを再度設定
	std::vector<int>::iterator itr2 = vec.begin();

	// 再度アドレス出力
	std::cout << "vector要素追加後" << std::endl;
    while (itr2 != vec.end())
    {
        std::cout << &(*itr2) << std::endl;
        itr2++;
    }

	for (int num:vec)
    {
        std::cout << to_string(num) << std::endl;
    }
}
--------------------------------------------------
出力例
--------------------------------------------------
vector
0x1596068c0
0x1596068c4
0x1596068c8
0x1596068cc
0x1596068d0

vector要素追加後
0x1596068e0
0x1596068e4
0x1596068e8
0x1596068ec
0x1596068f0
0x1596068f4
1
2
3
4
5
6

[C++] 参考サイトの選別

C++を体系的にまとめたサイトには説明が不十分なものがあったりします。

std::vectorの説明でコンテナを変更するメンバ関数がpush_backしかないような書き方をしているサイトがあったので、Chromeの拡張機能uBlacklistで検索結果に表示できなくしました。insertやeraseなどを明記しなくても、他にも関連するメンバ関数があることに触れて欲しかったです。

cpprefjp – C++日本語リファレンスが辞書的なサイトとして優れているので、検索する前にまずそこで調べることをお勧めします。

このサイトのような学習過程を記録した個人ブログも中級以上の方には何の参考にもならないですから検索結果に出ないようにすべきでしょう。

cpprefjp – C++日本語リファレンス

[C++] プログラミング学習 2022年7月

アプリ製作を中心にC++を学んできましたが、どうしてもスキルに偏りが生じるため、教材に従って抜けを埋めていきます。

内容へのアクセス性を重視し、以下の教材を電子書籍ではなく単行本として持っています。

1.プログラミング入門 B.ストラウストラップ著:C++07以前のレガシーC++
2.プログラミング言語C++ B.ストラウストラップ著:C++11対応
3.ゲームプログラミングC++

これまで作ってきたアプリではstructやenumなどユーザー定義型のデータをほとんど使っていません。

今回の学習ではこの辺りを中心に学んでいくつもりです。

[C++]『ゲームプログラミングC++』Chapter05 : SDLからOpenGLへの移行完了

[M1 Mac, Big Sur 11.6.7, clang 13.0.0, NO IDE]

Actor.hを修正することでAsteroidが動くようになりました。

GLEWおよびSDL関連ライブラリはHomebrewからインストール、SOILはGitHubから入手しビルドしました。

プログラム内で作業ディレクトリの変更さえできれば、IDEを使わなくても問題なく動作します。この本の想定読者はC++中級レベルなのでその点は大丈夫でしょう。

Apple Siliconでも正常に動作し一安心です。これでようやく次の章に進むことができます。

# コンパイラ設定
COMPILER = clang++
DEBUG = -g

# フラグ設定
CPPFLAGS = -std=c++11
LDFLAGS = -lc++ -framework CoreFoundation -framework OpenGL

# includeパス(-I)
INCLUDE = -I./include/act -I./include/base -I./include/compo -I./include/etc \
-I/opt/homebrew/Cellar/sdl2/2.0.22/include -I/opt/homebrew/Cellar/sdl2_image/2.0.5/include \
-I/opt/homebrew/Cellar/glew/2.2.0_1/include -I/opt/homebrew/Cellar/sdl2_mixer/2.0.4_3/include \
-I/opt/homebrew/Cellar/sdl2_ttf/2.0.18_1/include -I/opt/homebrew/Cellar/libpng/1.6.37/include \
-I/usr/local/include -I/code/cpp/mylib/include

# ライブラリ直接パス(-l)
LIBRARY_l = -lsdl2 -lsdl2_image -lglew  -lsdl2_mixer -lsdl2_ttf -lsoil -lpng16 \
/code/cpp/mylib/lib/split.dylib 

# ライブラリパス(-L)
LIBRARY_L = -L/opt/homebrew/Cellar/sdl2/2.0.22/lib -L/opt/homebrew/Cellar/sdl2_image/2.0.5/lib \
-L/opt/homebrew/Cellar/glew/2.2.0_1/lib -L/opt/homebrew/Cellar/sdl2_mixer/2.0.4_3/lib \
-L/opt/homebrew/Cellar/sdl2_ttf/2.0.18_1/lib -L/opt/homebrew/Cellar/libpng/1.6.37/lib \
-L/usr/local/lib \

# 実行ファイル設定
TARGET = Game
TARGETDIR = ./bin

# ソースコードパス
SRCROOT = ./src

# oファイルの出力ディレクトリ
OBJROOT = ./obj

# プロジェクト
PROJ = Chapter05_12

# ソースディレクトリのリスト化
SRCDIRS := $(shell find $(SRCROOT) -type d)

# ソースディレクトリから全てのcppファイルをリスト化
SRCS   = $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.cpp))

# cppファイルのリストからオブジェクトファイルをリスト化
OBJS   = $(addprefix $(OBJROOT), $(patsubst ./src%,%,$(SRCS:.cpp=.o)))

# oファイルの出力ディレクトリをリスト化
OBJDIRS   = $(addprefix $(OBJROOT), $(patsubst ./%,/%,$(SRCDIRS)))

# cppファイルからoファイル作成
$(OBJROOT)/%.o: $(SRCROOT)/%.cpp
	@if [ ! -e `dirname $@` ]; then mkdir -p `dirname $@`; fi
	$(COMPILER) $(CPPFLAGS) $(INCLUDE) $(DEBUG) $(OPTION) -o $@ -c $<

# oファイルから実行ファイル作成
$(TARGET):$(OBJS)
	$(COMPILER) -o $(TARGETDIR)/$@ $(OBJS) $(LIBRARY_l) $(LIBRARY_L) $(LDFLAGS)

# 全ソース・コンパイル&ビルド
.PHONY:all
all: clean $(OBJS) $(TARGET)

# oファイル・実行ファイル削除
.PHONY:clean
clean:
	rm -rf $(OBJS) $(TARGETDIR)/$(TARGET)

[C++]『ゲームプログラミングC++』Chapter05 : テクスチャマッピング

[M1 Mac, Big Sur 11.6.7, clang 13.0.0, NO IDE]

テクスチャマッピングがほぼ完了しました。

有効エリアがWindowの右上に位置していましたが、Asteroid.cppを修正して全面に表示させました。

ただAsteroidに動きがないのでまだOpenGLへの移行完了ではありません。

NO IDEでの学習ですから作業ディレクトリが異なるなど色々微調整が必要ですが、何とか手の内に入れつつあります。

Vector2 randPos = Random::GetVector(Vector2::Zero,
		Vector2(1024.0f, 768.0f));
Vector2 randPos = Random::GetVector(Vector2(-512.0f, -384.0f),
		Vector2(512.0f, 384.0f));
修正前
修正後

[C++]『ゲームプログラミングC++』Chapter05 : 変換行列

[M1 Mac, Big Sur 11.6.7, clang 13.0.0, NO IDE]

座標や回転角度が違うスプライトコンポーネント20個を描画させました(P177)。

どうも本のようにはなりません。と言いますか、この辺りの本の説明は手抜きがひどいです。こちらで考えて色々手を入れないと説明だけでは立ち往生します。SDLからOpenGLへの移行という大事なところですから丁寧に説明してもらいたいです。

あまり深入りせず、次のテクスチャマッピングへ進みます。

この本は独学には向かないとつくづく思います。精通した方のアシストがないととても読み進められないでしょう。学校の教材としてはそれなりに使えるのかもしれません。

Matrix4::CreateSimpleViewProj(1024.f, 768.f)では20個のうち7個しか描画できていない
Matrix4::CreateSimpleViewProjのサイズを縦横各4倍にして20個あることを確認

[C++]『ゲームプログラミングC++』 Game::initSpriteVertsの追加

[M1 Mac, Big Sur 11.6.7, clang 13.0.0, NO IDE]

前回の続きです。

描画した図形が平行四辺形になっていましたが、長方形になるように修正しました。

ソースコードにGame::initSpriteVertsを追加して、Game::Initializeにも記入します。書籍本文中にこの関数がソースコードに存在するかのように書いていますが、そんなものはありません(P145)。Game::CreateSpriteVertsを参考に自分で作りました。

void Game::initSpriteVerts()
{
	float vertexBuffer[]={
		-0.5f,0.5f,0.0f,  //頂点0
		0.5f,0.5f,0.0f,   //頂点1
		0.5f,-0.5f,0.0f,  //頂点2
		-0.5f,-0.5f,0.0f  //頂点3
	};
	unsigned int indexBuffer[]={
		0,1,2,
		2,3,0
	};

	mSpriteVerts = new VertexArray(vertexBuffer, 4, indexBuffer, 6);
}