ところが、このリストにpush_backして格納しようとするとDebugモードのデバッグ時に7~9割程度の確率でセグメンテーション違反が起きます。例えば次のようなエラーです。
Windows7(64bit), Visual Studio Express 2013で開発しています。VC++2012でも同様の例外を確認しました。文字コードはUnicodeを使用し、特に他のライブラリ等を使用していません。ハンドルされない例外が 0x0133297E (win32api_test.exe) で発生しました: 0xC0000005: 場所 0x005C0077 への書き込み中にアクセス違反が発生しました。
デバッグなしで開始したり、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;
};
#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);
}
- 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;
};
#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);
}
蛇足ながら同様の例外が発生するコードを挙げます。こちらは独自クラスではなくstd::unique_ptrも使わないで再現します。例外は、
と同じでした。場所 0x005C0077と、何回やっても同じなのでおそらくプログラムが悪いのだと思いますが、全く見当が付きません。ハンドルされない例外が 0x00E71B6E (ctest.exe) で発生しました: 0xC0000005: 場所 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;
};
#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);
}