函数指针返回值时出现奇怪的段错误
在学习 C# 时,我发现重新实现 List 或 LinkedList 之类的东西很有趣,只是为了了解它的工作原理以及实现它时可能遇到的潜在问题。
在学习 C++ 时,由于我有一些 C# 经验,我决定挑战自己并尝试实现比章节末尾活动要求的更高级的代码。因此,我最终尝试在 C++ 中实现一个非通用列表来尝试一下,但最终收到了一个非常奇怪的段错误。
关于代码的一个小免责声明,在尝试修复它时,我最终重构了它并删除了一些东西(不过,它们都没有改变错误),所以一两个函数没有用,但在尝试理解问题几个小时后,我不想删除或更改任何内容并意外地解决问题。无论如何,这是代码。
class List {
private:
int *ListData;
size_t ListSize;
size_t Pos;
std::stack<size_t> NullList;
size_t InternalNull();
inline size_t PosOnly();
void Check();
size_t (List::*NextNumber)();
public:
List(bool InternalNullHandle);
List(size_t DefaultSize, bool InternalNullHandle);
~List();
void Add(const int SalesRef);
int GetCopy(size_t Pos);
int Get();
};
List::List(bool InternalNullHandle) {
NextNumber = (InternalNullHandle) ? &List::InternalNull : &List::PosOnly;
ListSize = 32;
ListData = new int[32];
Pos = 0;
}
List::List(size_t DefaultSize, bool InternalNullHandle) {
NextNumber = (InternalNullHandle) ? &List::InternalNull : &List::PosOnly;
ListSize = DefaultSize;
ListData = new int[DefaultSize];
Pos = 0;
}
List::~List() {
delete[] ListData;
}
void List::Check() {
if (Pos >= ListSize) {
size_t OldSize = ListSize;
ListSize*=2;
int *Buffer = new int[ListSize];
memcpy(Buffer, ListData, sizeof(int)*OldSize);
if (ListData != NULL) {
delete[] ListData; //POINT OF INTEREST ONE
ListData = NULL;
}
else {
std::cerr<<"ListData is null."<<std::endl;
}
ListData = Buffer;
}
}
size_t List::InternalNull() {
if (NullList.size() != 0) {
size_t ToReturn = NullList.top();
NullList.pop();
return ToReturn;
}
return PosOnly();
}
inline size_t List::PosOnly() {
size_t Old = Pos;
++Pos;
Check();
return Old;
}
inline void List::Add(const int SalesRef) {
//size_t Value = (this->*NextNumber) ();
//ListData[Value] = SalesRef;
//if the above code is utilised instead, everything works fine
ListData[ (this->*NextNumber) () ] = SalesRef; //POINT OF INTEREST TWO
}
inline int List::GetCopy(size_t Pos) {
return ListData[Pos];
}
我通常不发帖,但广泛谷歌。我最终安装并运行了 valgrind,当使用兴趣点二时,它在兴趣点一处出现读取和写入错误。然而,当注释掉兴趣点二并使用注释行时,没有出现任何问题。
该问题仅在 128 次迭代后出现,这意味着它正确加倍到 64 和 128,并且正确删除了数组。
另外,需要注意的是,代码在使用 g++ 编译的 Windows 上运行得非常好。
我尝试使用单独的类重现该错误,但它工作得很好。
再说一遍,我知道我应该使用标准容器(我会的),但我喜欢了解一切,而我无法弄清楚这一点的事实非常烦人。再加上我什至无法重现它并且不得不复制这个不完整且设计糟糕的代码,只会让情况变得更糟。感谢您提前的帮助!
小编辑,如果真的很难阅读,我会添加注释并尝试清理代码而不破坏(好吧,更确切地说是修复)它。测试的操作系统是带有 mingw 的 Windows 7(可以运行)和带有 g++ 的 Debian(仅适用于未注释的注释行)。
While learning C#, I found it fun to reimplement things like List or LinkedList just to understand how it works and potential problems you may have while implementing it.
While learning C++, since I have some experience in C#, I decided to challenge myself and attempt to implement more advanced code than what the end of chapter activities ask. So, I ended up trying to implement a non generic list in C++ to try it out, but ended up receiving a very weird seg fault.
A small disclaimer on the code, while trying to fix it I ended up refactoring it and removing stuff (none of it changed the error, though) so a function or two has no use but after a few hours trying to understand the problem, I don't want to remove or change anything and accidently fix the problem. Anyway, here's the code.
class List {
private:
int *ListData;
size_t ListSize;
size_t Pos;
std::stack<size_t> NullList;
size_t InternalNull();
inline size_t PosOnly();
void Check();
size_t (List::*NextNumber)();
public:
List(bool InternalNullHandle);
List(size_t DefaultSize, bool InternalNullHandle);
~List();
void Add(const int SalesRef);
int GetCopy(size_t Pos);
int Get();
};
List::List(bool InternalNullHandle) {
NextNumber = (InternalNullHandle) ? &List::InternalNull : &List::PosOnly;
ListSize = 32;
ListData = new int[32];
Pos = 0;
}
List::List(size_t DefaultSize, bool InternalNullHandle) {
NextNumber = (InternalNullHandle) ? &List::InternalNull : &List::PosOnly;
ListSize = DefaultSize;
ListData = new int[DefaultSize];
Pos = 0;
}
List::~List() {
delete[] ListData;
}
void List::Check() {
if (Pos >= ListSize) {
size_t OldSize = ListSize;
ListSize*=2;
int *Buffer = new int[ListSize];
memcpy(Buffer, ListData, sizeof(int)*OldSize);
if (ListData != NULL) {
delete[] ListData; //POINT OF INTEREST ONE
ListData = NULL;
}
else {
std::cerr<<"ListData is null."<<std::endl;
}
ListData = Buffer;
}
}
size_t List::InternalNull() {
if (NullList.size() != 0) {
size_t ToReturn = NullList.top();
NullList.pop();
return ToReturn;
}
return PosOnly();
}
inline size_t List::PosOnly() {
size_t Old = Pos;
++Pos;
Check();
return Old;
}
inline void List::Add(const int SalesRef) {
//size_t Value = (this->*NextNumber) ();
//ListData[Value] = SalesRef;
//if the above code is utilised instead, everything works fine
ListData[ (this->*NextNumber) () ] = SalesRef; //POINT OF INTEREST TWO
}
inline int List::GetCopy(size_t Pos) {
return ListData[Pos];
}
I normally don't post, but google extensively. I ended up installing and running valgrind, which gave read and write errors at Point of Interest One when Point of Interest Two was used. However, when Point of Interest Two was commented out and the commented lines were used, no problems were given.
The problem only arises after 128 iterations, meaning it doubles to 64 and 128 correctly as well as deleted the arrays correctly.
Also, as a note, the code ran perfectly fine on Windows compiled with g++.
I tried to reproduce the error using a separate class, but it worked perfectly fine.
Again, I know I should use the standard containers (and I will) but I like to understand everything and the fact that I can't figure this out is extremely annoying. Coupled with the fact that I can't even reproduce it and had to copy this incomplete and badly designed code just makes it worse. Thanks for the help in advance!
Minor edit, if it's really hard to read I'll add comments and try to clean the code up without breaking (well, fixing, rather) it. The OSes it was tested on was Windows 7 with mingw (which worked) and Debian with g++ (which only worked with the commented lines uncommented).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
该语句的问题
在于,获取
ListData
字段的值和调用NextNumber
指向的成员函数之间没有序列点。因此,编译器非常乐意在函数调用之前进行加载,然后在调用之后对其进行索引。但是,该调用可能会导致重新分配 ListData,因此调用之前获得的指针现在悬空(它指向刚刚删除的数组),并且会发生不好的事情。使用注释掉的代码,您可以强制函数调用在获取
ListData
之前发生,以便在调整大小重新分配后获取始终获得正确的值。The problem with the statement
is that there's no sequence point between fetching the value of the field
ListData
and calling the member function pointed at byNextNumber
. So the compiler is perfectly happy doing that load before the function call and then doing the indexing off of it after the call. However, that call may result in reallocating ListData, so the pointer it got from before the call is now dangling (it points at the just deleted array) and bad things happen.With the commented out code, you force the function call to occur before the fetch of
ListData
, so that fetch will always get the right value after the resize reallocation.