VC++のデバッグ時にstd::listのpush_backでセグメンテーション違反が発生します

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
アバター
milfeulle
記事: 47
登録日時: 11年前
住所: マリーランド
連絡を取る:

VC++のデバッグ時にstd::listのpush_backでセグメンテーション違反が発生します

#1

投稿記事 by milfeulle » 11年前

初めまして。Win32APIを勉強しています。ウィンドウ(ボール)を作って画面内で動かすプログラムを作ろうと思い、ウィンドウを管理するクラスMovingBallをstd::listに格納しました。

ところが、このリストにpush_backして格納しようとするとDebugモードのデバッグ時に7~9割程度の確率でセグメンテーション違反が起きます。例えば次のようなエラーです。
ハンドルされない例外が 0x0133297E (win32api_test.exe) で発生しました: 0xC0000005: 場所 0x005C0077 への書き込み中にアクセス違反が発生しました。
Windows7(64bit), Visual Studio Express 2013で開発しています。VC++2012でも同様の例外を確認しました。文字コードはUnicodeを使用し、特に他のライブラリ等を使用していません。

デバッグなしで開始したり、Releaseモードで実行した場合はこのような例外が発生しませんでした。この例外は時間を空ける・空けないにかかわらず1回目の格納の際に発生しました。また、1回成功するとそれ以降は必ず成功しました。(例外は以降発生しませんでした)

ソースファイルはmain.cppとmain.h及びmoving_ball.cppとmoving_ball.hで構成されています。例外はmain.cppのMainWindow::onLButtonUp()で起こりました。ソースファイルの内部は後述します。

[hr]

std::list<std::unique_ptr<MovingBall>>のstd::unique_ptr関連の問題かと思いましたので、std::list<MovingBall*>としたところ例外が発生しなくなりました。また、std::unique_ptrを用いて適当な空のクラスやプリミティブな型(int等)でstd::list<std::unique_ptr<C>>としても例外は発生しませんでした。(ただし再現する最小のコードを文末に掲載しますが、こちらではstd::list<int*>でも例外が起こります。)

これらから、make_uniqueやnewなどによってメモリをヒープに確保したときに何らかの問題が発生していると考えました。ウィンドウプロシージャの中でpush_backすると発生する模様です。ただ、調べても同様の例が見当たりませんでした。

いろいろ試行錯誤しましたが原因が突き止められませんでしたので、もしも何かお気づきの点などがございましたら教えて頂けるとありがたく思います。よろしくお願いいたします。

[hr]
ソースファイル群

- main.h

コード:

#pragma once
#include <Windows.h>
#include <exception>
#include <memory>
#include <list>

#include "moving_ball.h"
class C { };
class MainWindow {
public:
	MainWindow(HINSTANCE h_instance);
	void create(WCHAR* title);
	void show();

protected:
	void onCreate();
	void onLButtonUp();

private:
	static LRESULT CALLBACK WindowProc(HWND h_wnd, UINT msg, WPARAM wparam, LPARAM lparam);

	LRESULT mainProc(HWND h_wnd, UINT msg, WPARAM wp, LPARAM lp);

	std::list<std::unique_ptr<MovingBall>> moving_balls;
	std::list<std::unique_ptr<C>> mylist; // テスト用
	WNDCLASS wndc;
	HWND h_wnd;
	SHORT width;
	SHORT height;
};
- main.cpp

コード:

#include "main.h"

int WINAPI WinMain(HINSTANCE h_instance, HINSTANCE h_prev_instance, LPSTR command_line, int command_show) {
	try {
		MainWindow main_window(h_instance);

		main_window.create(L"milfeulle");
		main_window.show();
	}
	catch(const std::exception&) {
		return -1;
	}

	MSG msg;
	int result;

	while(TRUE) {
		result = GetMessage(&msg, NULL, 0, 0);

		if(result == 0 || result == -1) {
			break;
		}

		DispatchMessage(&msg);
	}

	return msg.wParam;
}


MainWindow::MainWindow(HINSTANCE h_instance) {
	wndc.style = CS_HREDRAW | CS_VREDRAW;
	wndc.lpfnWndProc = WindowProc;
	wndc.cbClsExtra = wndc.cbWndExtra = 0;
	wndc.hInstance = h_instance;
	wndc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndc.lpszMenuName = NULL;
	wndc.lpszClassName = L"MainWindow";

	if(!RegisterClass(&wndc)) {
		throw std::exception();
	}

	width = 600;
	height = 400;
}

void MainWindow::create(WCHAR* title) {
	CreateWindow(
		wndc.lpszClassName, title,
		WS_OVERLAPPEDWINDOW,
		100, 100, width, height, NULL, NULL,
		wndc.hInstance, this);

	// ここに来るのはWM_CREATEメッセージの処理の後
	if(h_wnd == NULL) {
		throw std::exception();
	}
}

void MainWindow::show() {
	ShowWindow(h_wnd, SW_SHOW);
}


void MainWindow::onCreate() {
	MovingBall::Register();
}

void MainWindow::onLButtonUp() {
	moving_balls.push_back(std::make_unique<MovingBall>(h_wnd));
}

LRESULT CALLBACK MainWindow::WindowProc(HWND h_wnd, UINT msg, WPARAM wp, LPARAM lp) {
	MainWindow* self = (MainWindow*)GetWindowLong(h_wnd, GWL_USERDATA);

	if(self == NULL) {
		if(msg == WM_CREATE) {
			self = (MainWindow*)((LPCREATESTRUCT)lp)->lpCreateParams;

			SetWindowLong(h_wnd, GWL_USERDATA, (LPARAM)self);
			self->h_wnd = h_wnd;

			self->onCreate();
		}

		return DefWindowProc(h_wnd, msg, wp, lp);
	}

	else {
		return self->mainProc(h_wnd, msg, wp, lp);
	}
}

LRESULT MainWindow::mainProc(HWND h_wnd, UINT msg, WPARAM wp, LPARAM lp) {
	switch(msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_LBUTTONUP:
		onLButtonUp();
	}

	return DefWindowProc(h_wnd, msg, wp, lp);
}
[hr]

- moving_ball.h

コード:

#pragma once

#include <Windows.h>

class MovingBall {
public:
	static void Register();
	
	MovingBall(HWND h_wnd);
	~MovingBall();
	
private:
	static LRESULT CALLBACK WindowProc(HWND h_wnd, UINT msg, WPARAM wparam, LPARAM lparam);

	LRESULT mainProc(HWND h_wnd, UINT msg, WPARAM wp, LPARAM lp);

	HWND h_wnd;
};
- moving_ball.cpp

コード:

#include "moving_ball.h"
#include <exception>

void MovingBall::Register() {
	WNDCLASS wndc;

	wndc.style = CS_DBLCLKS;
	wndc.lpfnWndProc = WindowProc;
	wndc.cbClsExtra = wndc.cbWndExtra = 0;
	wndc.hInstance = NULL;
	wndc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	wndc.lpszMenuName = NULL;
	wndc.lpszClassName = L"MovingBall";

	if(!RegisterClass(&wndc)) {
		throw std::exception();
	}
}

MovingBall::MovingBall(HWND h_wnd) {
	static int x = 100, y = 100;

	CreateWindow(
		L"MovingBall", NULL,
		WS_CHILD | WS_VISIBLE,
		x, y, 10, 10, h_wnd, NULL,
		GetModuleHandle(NULL), this);
	
	x += 20;
	y += 10;
	// ここに来るのはWM_CREATEメッセージの処理の後
	if(this->h_wnd == NULL) {
		throw std::exception();
	}
}

LRESULT CALLBACK MovingBall::WindowProc(HWND h_wnd, UINT msg, WPARAM wp, LPARAM lp) {
	MovingBall* self = (MovingBall*)GetWindowLong(h_wnd, GWL_USERDATA);

	if(self == NULL) {
		if(msg == WM_CREATE) {
			self = (MovingBall*)((LPCREATESTRUCT)lp)->lpCreateParams;

			SetWindowLong(h_wnd, GWL_USERDATA, (LPARAM)self);
			self->h_wnd = h_wnd;
		}

		return DefWindowProc(h_wnd, msg, wp, lp);
	}

	else {
		return self->mainProc(h_wnd, msg, wp, lp);
	}
}

LRESULT MovingBall::mainProc(HWND h_wnd, UINT msg, WPARAM wp, LPARAM lp) {
	switch(msg) {

	}

	return DefWindowProc(h_wnd, msg, wp, lp);
}

MovingBall::~MovingBall() {
	DestroyWindow(h_wnd);
}
[hr]

蛇足ながら同様の例外が発生するコードを挙げます。こちらは独自クラスではなくstd::unique_ptrも使わないで再現します。例外は、
ハンドルされない例外が 0x00E71B6E (ctest.exe) で発生しました: 0xC0000005: 場所 0x005C0077 への書き込み中にアクセス違反が発生しました。
と同じでした。場所 0x005C0077と、何回やっても同じなのでおそらくプログラムが悪いのだと思いますが、全く見当が付きません。

- main.h

コード:

#pragma once
#include <Windows.h>
#include <exception>
#include <list>

class MainWindow {
public:
	MainWindow(HINSTANCE h_instance);
	void create(WCHAR* title);
	void show();

protected:
	void onCreate();
	void onLButtonUp();

private:
	static LRESULT CALLBACK WindowProc(HWND h_wnd, UINT msg, WPARAM wparam, LPARAM lparam);

	LRESULT mainProc(HWND h_wnd, UINT msg, WPARAM wp, LPARAM lp);

	std::list<int*> moving_balls;

	WNDCLASS wndc;
	HWND h_wnd;
	SHORT width;
	SHORT height;
};
- main.cpp

コード:

#include "main.h"

int WINAPI WinMain(HINSTANCE h_instance, HINSTANCE h_prev_instance, LPSTR command_line, int command_show) {
	try {
		MainWindow main_window(h_instance);

		main_window.create(L"milfeulle");
		main_window.show();
	}
	catch(const std::exception&) {
		return -1;
	}

	MSG msg;
	int result;

	while(TRUE) {
		result = GetMessage(&msg, NULL, 0, 0);

		if(result == 0 || result == -1) {
			break;
		}

		DispatchMessage(&msg);
	}

	return msg.wParam;
}


MainWindow::MainWindow(HINSTANCE h_instance) {
	wndc.style = CS_HREDRAW | CS_VREDRAW;
	wndc.lpfnWndProc = WindowProc;
	wndc.cbClsExtra = wndc.cbWndExtra = 0;
	wndc.hInstance = h_instance;
	wndc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndc.lpszMenuName = NULL;
	wndc.lpszClassName = L"MainWindow";

	if(!RegisterClass(&wndc)) {
		throw std::exception();
	}

	width = 600;
	height = 400;
}

void MainWindow::create(WCHAR* title) {
	CreateWindow(
		wndc.lpszClassName, title,
		WS_OVERLAPPEDWINDOW,
		100, 100, width, height, NULL, NULL,
		wndc.hInstance, this);

	// ここに来るのはWM_CREATEメッセージの処理の後
	if(h_wnd == NULL) {
		throw std::exception();
	}
}

void MainWindow::show() {
	ShowWindow(h_wnd, SW_SHOW);
}


void MainWindow::onCreate() {
}

void MainWindow::onLButtonUp() {
	moving_balls.push_back(new int(10));
}

LRESULT CALLBACK MainWindow::WindowProc(HWND h_wnd, UINT msg, WPARAM wp, LPARAM lp) {
	MainWindow* self = (MainWindow*)GetWindowLong(h_wnd, GWL_USERDATA);

	if(self == NULL) {
		if(msg == WM_CREATE) {
			self = (MainWindow*)((LPCREATESTRUCT)lp)->lpCreateParams;

			SetWindowLong(h_wnd, GWL_USERDATA, (LPARAM)self);
			self->h_wnd = h_wnd;

			self->onCreate();
		}

		return DefWindowProc(h_wnd, msg, wp, lp);
	}

	else {
		return self->mainProc(h_wnd, msg, wp, lp);
	}
}

LRESULT MainWindow::mainProc(HWND h_wnd, UINT msg, WPARAM wp, LPARAM lp) {
	switch(msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_LBUTTONUP:
		onLButtonUp();
	}

	return DefWindowProc(h_wnd, msg, wp, lp);
}
ζ*'ヮ')ζプログラミングはみんなで奏でるシンフォニー

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: VC++のデバッグ時にstd::listのpush_backでセグメンテーション違反が発生します

#2

投稿記事 by usao » 11年前

>MainWindow* self = (MainWindow*)GetWindowLong(h_wnd, GWL_USERDATA);

64bit版でビルドされているとかいうことはありませんか?
対してGetWindowLongの戻り値が32bitでポインタを入れるにはどうの,みたいな.

アバター
milfeulle
記事: 47
登録日時: 11年前
住所: マリーランド
連絡を取る:

Re: VC++のデバッグ時にstd::listのpush_backでセグメンテーション違反が発生します

#3

投稿記事 by milfeulle » 11年前

ご指摘ありがとうございます。記載忘れましたが32bitでビルドしています。64bitとの互換性のために
  • GetWindowLongPtr
  • SetWindowLongPtr
を使ってみましたが結果は変わりませんでした。通常のウィンドウプロシージャは問題なく動いているようです。

そういえばstd::listではなくstd::vectorだと何も起こりませんでしたが何か関係があるのでしょうか……。
ζ*'ヮ')ζプログラミングはみんなで奏でるシンフォニー

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: VC++のデバッグ時にstd::listのpush_backでセグメンテーション違反が発生します

#4

投稿記事 by usao » 11年前

謎ですね.こういうのは大丈夫なのでしょうか?

コード:

int WINAPI WinMain( ... )
{
  MainWindow main_window( h_instance );
  main_window.moving_balls.push_back( new int(10) );  //とりあえずpublicにして…
  ...
}
だとか
int WINAPI WinMain( ... )
{
  std::list< int * > List;
  List.push_back( new int(10) );
  ...
}

アバター
a5ua
記事: 199
登録日時: 14年前

Re: VC++のデバッグ時にstd::listのpush_backでセグメンテーション違反が発生します

#5

投稿記事 by a5ua » 11年前

マズイところを指摘します。(下記ソースコードの、//// コメントの部分)

コード:

int WINAPI WinMain(HINSTANCE h_instance, HINSTANCE h_prev_instance, LPSTR command_line, int command_show) {
    try {
        MainWindow main_window(h_instance); //// このオブジェクトの寿命は、tryブロック内部
 
        main_window.create(L"milfeulle");
        main_window.show();
    }
    catch(const std::exception&) {
        return -1;
    }
    //// main_windowは使えない。しかし、ウィンドウプロシージャ内でポインタを取得できてしまう。
    //// MainWindow* self = (MainWindow*)GetWindowLong(h_wnd, GWL_USERDATA);
    //// すなわち、MainWindow::onLButtonUp()では、寿命が尽きたオブジェクトのメンバー(moving_balls)を操作してしまっている。
 
    MSG msg;
    int result;
 
    while(TRUE) {
        result = GetMessage(&msg, NULL, 0, 0);
 
        if(result == 0 || result == -1) {
            break;
        }
 
        DispatchMessage(&msg);
    }
 
    return msg.wParam;
}
とりあえずの対策として、main_windowをtryブロックの外に出してみるのはいかがでしょうか?

コード:

#include "main.h"
 
int WINAPI WinMain(HINSTANCE h_instance, HINSTANCE h_prev_instance, LPSTR command_line, int command_show) {
	MainWindow main_window(h_instance);
	try {
        main_window.create(L"milfeulle");
        main_window.show();
    }
    catch(const std::exception&) {
        return -1;
    }
 
    MSG msg;
    int result;
 
    while(TRUE) {
        result = GetMessage(&msg, NULL, 0, 0);
 
        if(result == 0 || result == -1) {
            break;
        }
 
        DispatchMessage(&msg);
    }
 
    return msg.wParam;
}

アバター
milfeulle
記事: 47
登録日時: 11年前
住所: マリーランド
連絡を取る:

Re: VC++のデバッグ時にstd::listのpush_backでセグメンテーション違反が発生します

#6

投稿記事 by milfeulle » 11年前

ご指摘ありがとうございます。とても基礎的な部分で躓いていました。確かにスコープを抜けた後の動作が不定でした。全てtry文の中で処理することにより、あるいはtry文から外すことによりエラーが出なくなりました。

何時間も悩んでいたことがこのような致命的なミスだったとは……。本当にありがとうございました!
ζ*'ヮ')ζプログラミングはみんなで奏でるシンフォニー

閉鎖

“C言語何でも質問掲示板” へ戻る