ねむみ高まる

文章力がないので、文章を書く練習をしています。

LNK2019 未解決の外部シンボル _main が関数~~というビルドエラー

Visual Studio C++C++のプロジェクトをビルドして、LNK2019 未解決の外部シンボル _main が関数~~というエラーが出る場合はエントリポイントがWinMain()ではなくmain()になっています。

 

プロジェクトのプロパティ → リンク → システム → サブシステムでコンソール (/SUBSYSTEM:CONSOLE)となっているところをWindows (/SUBSYSTEM:WINDOWS)にすれば解決します。

 

参考文献

blogs.osdn.jp

【C++】delete後のダングリングポインタにアクセスしてみた

メモリ解放後、無効なアドレスを指しているポインタのことを「ダングリングポインタ」と一般的には呼ぶらしいです。

ダングリングポインタにアクセスするとどうなるの??という感じだったので調べてみました。 環境はMicrosoft Visual Studio Community 2015、Debug実行、x86(32bit)です。

まず、下記のソースコードのように、new int → deleteしてからアクセスしてみます。

#include <iostream>

using namespace std;

int main()
{
    int *a = new int(4);

    delete a;

    cout << *a << endl;

    return 0;
}

delete a;のところでブレークポイントを貼って実行してみると、次の画像のようになりました。 アドレス0x00DD6188に4byteで確保されています。前後は no man's land(0xfdfdfdfd)ですね。

f:id:nemumitakamaru:20190115223553p:plain

F5を押して実行してみると、次の画像のような感じでクラッシュしました。

f:id:nemumitakamaru:20190115223734p:plain

aが0x8123??0x00DD6188から変わってる??と思ってGoogleで検索を掛けてみると、下記のページを見つけました。

stackoverflow.com

Security Development Lifecycle (SDL) checksというものが有効になっていると、delete後、勝手に無効な値(0x8123)が入るようです。 これ有効にしておけば、ダングリングポインタにアクセスすることがなくなるので良いですね。

今回はダングリングポインタにアクセスしたいのでSDL checksを外します。 外し方は上記のサイトより引用↓

PROJECT -> Properties -> Configuration Properties -> C/C++ -> General -> SDL checks

SDL checksを外してから実行すると、無事?クラッシュすることもなく、ダングリングポインタにアクセスし、標準出力に値を出すことに成功しました。 deleteすると元の箇所は0xddで埋められるので、0xddddddddとなり、-572662307という数値になっています。

f:id:nemumitakamaru:20190115230422p:plain

また、cout << *a << endl;したあと、不思議なことに0x00DD6188周辺のメモリの値が色々と書き換えられていました。。何故そのようなことになっているかは、また今後調べてみます。

【C++】no man's land(0xfdfdfdfd)等を変更したときの挙動を見てみた

newの挙動について最近勉強していました。

newしたとき、自分が割り当てた変数以外の様々な値が変更されており???となっていたのですが、 teratailで質問してみたところ、複数の方々より、newの挙動についてご教授頂きました(多謝)。

C++ - newしたときに変更される値について|teratail

VC++のDebug実行では、newしたときメモリの上下に、管理情報としてno man's landという0xfdfdfdfdや確保したバイト数等が割り当てられることを知りました。
そこで、今回はそれらの値を変更してみて、どういった挙動になるかを見てみます。環境はMicrosoft Visual Studio Community 2015、Debug実行で行います。

afterのno man's landを変更

#include <iostream>

using namespace std;

int main()
{
    bool* heapValue;

    heapValue = new bool(true);

    // afterのno man's landを変更する
    *(heapValue + 1) = true;

    delete heapValue;

    return 0;
}

delete heapValueするときにクラッシュし、下記のエラーログが出ました。

HEAP CORRUPTION DETECTED: after Normal block (#150) at 0x0101C178.
CRT detected that the application wrote to memory after end of heap buffer.

afterとなっているので、正しいです。

beforeのno man's landを変更

#include <iostream>

using namespace std;

int main()
{
    bool* heapValue;

    heapValue = new bool(true);

    // beforeのno man's landを変更する
    *(heapValue - 1) = true;

    delete heapValue;

    return 0;
}

delete heapValueするときにクラッシュし、下記のエラー文が出ました。

HEAP CORRUPTION DETECTED: before Normal block (#150) at 0x00E9C170.
CRT detected that the application wrote to memory before start of heap buffer

beforeとなっているので、正しいです。

no man's landを微妙に超えた位置を変更すると?

#include <iostream>

using namespace std;

int main()
{
    bool* heapValue;

    heapValue = new bool(true);

    // no man's landでない範囲外の値を0x00→0x01に変更してみる
    *(heapValue - 5) = true;

    delete heapValue;

    return 0;
}

特に何も言われることなく、実行が無事?成功しました。
無事成功してしまうと、何かしらバグったときに調べるのが大変そうですね。。

バイト数(おそらく)を格納している箇所を1→0に

#include <iostream>

using namespace std;

int main()
{
    bool* heapValue;

    heapValue = new bool(true);

    // メモリのbeforeのno man's landを変更する
    *(heapValue - 12) = 0;

    delete heapValue;

    return 0;
}

メモリ配置は下記です。ここでは0x0132E5D8にheapValueの値が配置され、offset-12した0x0132E5CCの箇所にバイト数が格納されています。

*(heapValue - 12) = 0;を行う前

0x0132E5C0  00 00 00 00 00 00 00 00  
0x0132E5C8  01 00 00 00 01 00 00 00  
0x0132E5D0  96 00 00 00 fd fd fd fd  
0x0132E5D8  01 fd fd fd fd dd dd dd

*(heapValue - 12) = 0;を行った後

0x0132E5C0  00 00 00 00 00 00 00 00  
0x0132E5C8  01 00 00 00 00 00 00 00  
0x0132E5D0  96 00 00 00 fd fd fd fd  
0x0132E5D8  01 fd fd fd fd dd dd dd 

delete heapValueするときにクラッシュし、下記のエラー文が出ました。

HEAP CORRUPTION DETECTED: after Normal block (#150) at 0x0132E5D8.
CRT detected that the application wrote to memory after end of heap buffer.

afterのヒープのメモリが書き換えられているのでは?(意訳)となっており正しくないです。

関係ない話ですが、バイト数は実際の値を見るとoffset-12のところに配置されているっぽいです。しかし、 Win32 Debug CRT Heap Internalsを読んでみるとoffset-16と書いており謎です。仕様が変わったのでしょうか?

参考文献

バッファオーバーフローを発生させて、その様子をVisual Studioのメモリウィンドウで見てみた

バッファオーバーフローとは??という感じだったので理解を深めるために、スタックベースのバッファオーバーフローを実際に発生させてみて、その様子をVisual Studioのメモリウィンドウで見てみます。

バッファオーバーフローを発生させるC++ソースコード

#include <iostream>

using namespace std;

void bufferOverFlow(void) {
    char byte[4] = {0xaa, 0xab, 0xac, 0xad};
    memset(byte, 0, 5);
}

int main() {
    bufferOverFlow();
    return 0;
}

byte変数はchar型の4byteの配列なのですが、memset(byte, 0, 5);とし、1byte分範囲外を0に設定するようにし、バッファオーバーフローするようにしています。 memset(byte, 0, 5);の行にブレークポイントをはって実行し、byte変数のアドレスをメモリウィンドウで表示してみると下図のように表示されました。

f:id:nemumitakamaru:20190112162027p:plain

char byte[4] = {0xaa, 0xab, 0xac, 0xad};の部分がちゃんと確保されていることが分かりました。

F10を押してステップ実行しmemset(byte, 0, 5);箇所を実行すると、下図のようになりました。

f:id:nemumitakamaru:20190112162316p:plain

0x006FFBC0  00 00 00 00 00 cc cc cc a1 e7 95 6d a0 fc 6f 00  .....フフフ。・・m.・o.
0x006FFBD0  33 1f 9e 00 55 10 9e 00 55 10 9e 00 00 00 52 00  3.・.U.・.U.・...R.

char byte[4] = {0xaa, 0xab, 0xac, 0xad};で確保した箇所が綺麗さっぱり消え、かつ範囲外でccとなっていた部分も消えました。変更された箇所が赤字になっていて見やすいですね。

更にステップ実行すると、下図のポップアップが表示され、クラッシュしました。

f:id:nemumitakamaru:20190112162658p:plain

スタックが破壊されると、それを検知して下記のエラー文が表示されるっぽいです。

Run-Time Check Failure #2 - Stack around the variable '変数名' was corrupted.

OpenGL/GLUT入門が大体終わりました

HSPやUnityからゲームプログラミングに入った結果、射影変換??みたいな感じになっており色々まずいと感じていたため、最近はOpenGL(GLUT)の勉強をしていました。 下記の2つサイトの入門講座をわからない部分は飛ばしながらも、一通り終わらせました。どちらのサイトも非常に勉強になりました。

次、GLUTで簡単なゲームを作ってみようと思います。

そういえば、GLUTはサポートが打ち切られ、代替としてGLFWが良いみたいな感じらしいですが、初心者にはシェーダーを学ばなければならないのが非常に重い気がします…モデルビュー変換・射影変換がよく分からないから勉強したい!となってやっているのに、先にシェーダーってのが辛いですね。

【OpenGL/GLUT】raw画像をテクスチャマッピングすると上下反転した

テクスチャマッピングの知識があまりない状態でGLUTテクスチャマッピングをやってみたら、何故か画像が上下反転しました。

f:id:nemumitakamaru:20181224111825p:plain

上下反転するソースコード

#include <stdio.h>
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>

namespace {
const int TEX_SIZE = 256;
const int WINDOW_SIZE = 256;

GLubyte pixels[TEX_SIZE][TEX_SIZE][4];
GLuint texName;
}

void disp()
{
    glClear(GL_COLOR_BUFFER_BIT);
    glBindTexture(GL_TEXTURE_2D, texName);

    glBegin(GL_POLYGON);
    {
        // 間違っていた箇所
        glTexCoord2f(0.0f, 0.0f); glVertex2f(-1.0f, -1.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex2f(-1.0f, 1.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex2f(1.0f, 1.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex2f(1.0f, -1.0f);
    }
    glEnd();
    glFlush();
}

int main(int argc, char ** argv)
{
    FILE *fp;

    errno_t error = fopen_s(&fp, "miya.data", "rb");
    if (error != 0) {
        return -1;
    }

    fread(pixels, sizeof pixels, 1, fp);
    fclose(fp);

    glutInit(&argc, argv);
    glutInitWindowSize(WINDOW_SIZE, WINDOW_SIZE);
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);

    glutCreateWindow("テクスチャマッピング");
    glutDisplayFunc(disp);

    glEnable(GL_TEXTURE_2D);
    glGenTextures(1, &texName);
    glBindTexture(GL_TEXTURE_2D, texName);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEX_SIZE, TEX_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glutMainLoop();

    glDeleteTextures(1, &texName);

    return 0;
}

uv座標は左下が原点のはずなので、頂点をその順に対応させて貼り付ければ大丈夫なはずでは…と思っていたのですがそんなことはありませんでした。

ぐーぐる先生で検索をかけてみると、床井先生のサイトで下記のような説明を見つけました。
床井研究室 - 第2回 テクスチャの割り当て

ただし,画像の原点(画像ファイルの最初の画素)の位置は通常画像の左上隅ですが,テクスチャ空間上では原点はテクスチャの左下にあるので,このプログラムの方法で画像を読み込んだ場合,画像は上下反転した形で読み込まれています.ポリゴンの頂点にテクスチャ座標を割り当てるときは,このことを考慮に入れる必要があります.たとえば,上図のように四角形ポリゴンの全面にテクスチャを貼り付けようとしたとき,ポリゴンの左下の頂点 (-1, -1, 0) に対応するテクスチャ座標は,テクスチャの左上 (0, 1) になります.

画像を普通に読み込むと上下反転するんですね。
結局のところ、正確には違いますが、uv座標も左上原点と考えると実装するときはわかりやすい気がします。

というわけで、頂点を指定している箇所のみ下記のように変更すると、反転せずに表示することができました。

glTexCoord2f(0.0f, 1.0f); glVertex2f(-1.0f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex2f(-1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex2f(1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex2f(1.0f, -1.0f);

f:id:nemumitakamaru:20181224113910p:plain

画像は下記のゲームのものを使わせていただきました(美也可愛い)。
アマガミ ちょっとおまけ劇場 EX3

【C++】クラスのメンバ変数のコンストラクタは宣言順に呼ばれ、デストラクタはその逆順に呼ばれる

クラスのメンバ変数の知識があやふやだったので、下のようなソースコードを書いて実験してみました。

aaa.cpp

#include <iostream>

using namespace std;

class A {
public:
    A() {
        cout << "A::A()" << endl;
    }
    ~A() {
        cout << "A::~A()" << endl;
    }
};

class B {
public:
    B() {
        cout << "B::B()" << endl;
    }
    ~B() {
        cout << "B::~B()" << endl;
    }
};

class C {
public:
    C() {
        cout << "C::C()" << endl;
    }
    ~C() {
        cout << "C::~C()" << endl;
    }
};

class AAA {
public:
    AAA()
    : a()
    , c() // cとbの初期化子リストの順番をあえて逆にしてみる
    , b()
    {
        cout << "AAA::AAA()" << endl; 
    }
    ~AAA() {
       cout << "AAA::~AAA()" << endl; 
    }
private:
    A a;
    B b;
    C c;
};

int main() {
    AAA aaa;
    return 0;
}

実行結果

A::A()
B::B()
C::C()
AAA::AAA()
AAA::~AAA()
C::~C()
B::~B()
A::~A()

というわけで、メンバ変数の宣言順にコンストラクタは呼ばれ、その逆順でデストラクタが呼ばれました。 なんと、初期化子リストの並びは関係ありません。。。かなり間違えやすいと思います。

ちなみにコンパイル時に clang++ -Wall aaa.cppのように -Wallを付けると、 下のようにwarningがちゃんと出ました。付けてないと出ませんでした。

aaa.cpp:39:7: warning: field 'c' will be initialized after field 'b' [-Wreorder]
    , c() // cとbの初期化子リストの順番をあえて逆にしてみる