您可以对 windows 中的字符串进行 `memset()` 吗?
You can `memset()` over a string in windows?
我正在努力将一些移植到 linux,我在代码中发现了一个相当有趣的错误,它显然适用于 windows,但不适用于 linux。带有一些字符串成员的 class 正在通过 memset(this)
初始化,这显然适用于 windows,但在字符串析构函数中的 linux 上抛出分段错误。
是的,我知道使用 memset()
这是一种糟糕的做法,我正在修复它。
SSCCE:
#include <iostream>
#include <cstring>
int main()
{
std::string tmp;
std::cout << "String instantiated" << std::endl;
memset(&tmp, 0, sizeof(tmp));
std::cout << "String memset" << std::endl;
return 0;
}
这在 windows 上运行良好,但字符串析构函数在 linux 上出现段错误。
编译器:
- MSVC++ 2013(Microsoft (R) C/C++ 针对 x64 优化编译器版本 18.00.31101)
- g++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2
我知道这曾经是(现在也是)糟糕的做法,但它最初是如何运作的?
如果您使用调试器检查程序退出时(在 return 0),您实际上遇到了段错误,因为程序正在尝试释放它认为打开的 std::string tmp堆栈然后失败,因为它在该位置找不到有效的 std::string。
编辑:
它可能适用于一个 OS 而不是另一个,因为它不是由 C++ 标准指定的,并且释放 std::string 的任何具体实现都可能不同,只要它们符合标准。碰巧你在 Linux 上使用的实现并不能很好地处理这个问题,但它不需要,因为这不是应该使用 memset 的情况。如果要在字符串上使用 memset,请使用 C 样式字符串。
如果您想深入了解实现细节,MSVC 和 Clang(使用 libc++)使用 string
和短字符串优化,大致如下所示:
class string {
size_t length;
char* ptr;
char short_buf[N];
};
所以如果它是 memset
到 0
,它的析构函数会认为它的长度为零并且可能什么都不做,而且即使它试图 delete[] ptr
,它也赢了不会崩溃,因为 delete
可以很好地处理空指针。
GCC 相反,直到最近才使用完全不同的 string
实现,其中涉及写时复制和引用计数。所以它的内部结构要复杂得多,它在 memset
.
之后崩溃也就不足为奇了
您问的是:
how did it ever work in the first place?
考虑如下实现:
class string
{
public:
string() : size_(0), data_(nullptr) {}
string(char const* s) : size_(strlen(s)), data_(new char(size_+1))
{
strcpy(data_, s);
}
~string()
{
if ( data_ )
{
delete [] data_;
}
}
private:
size_t size_;
char* data_;
};
给定这样的实现,如果 nullptr
由 0
表示(这是最常见的表示),
string s1;
memset(&s1, 0, sizeof(s1)); // Has no impact on s1
string s2("This is a test");
memset(&s2, 0, sizeof(s2)); // Makes s2 the same as a default
// constructed string with memory leak
// as a side effect. Still, it is
// not going to cause segementation
// fault.
我正在努力将一些移植到 linux,我在代码中发现了一个相当有趣的错误,它显然适用于 windows,但不适用于 linux。带有一些字符串成员的 class 正在通过 memset(this)
初始化,这显然适用于 windows,但在字符串析构函数中的 linux 上抛出分段错误。
是的,我知道使用 memset()
这是一种糟糕的做法,我正在修复它。
SSCCE:
#include <iostream>
#include <cstring>
int main()
{
std::string tmp;
std::cout << "String instantiated" << std::endl;
memset(&tmp, 0, sizeof(tmp));
std::cout << "String memset" << std::endl;
return 0;
}
这在 windows 上运行良好,但字符串析构函数在 linux 上出现段错误。
编译器:
- MSVC++ 2013(Microsoft (R) C/C++ 针对 x64 优化编译器版本 18.00.31101)
- g++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2
我知道这曾经是(现在也是)糟糕的做法,但它最初是如何运作的?
如果您使用调试器检查程序退出时(在 return 0),您实际上遇到了段错误,因为程序正在尝试释放它认为打开的 std::string tmp堆栈然后失败,因为它在该位置找不到有效的 std::string。
编辑:
它可能适用于一个 OS 而不是另一个,因为它不是由 C++ 标准指定的,并且释放 std::string 的任何具体实现都可能不同,只要它们符合标准。碰巧你在 Linux 上使用的实现并不能很好地处理这个问题,但它不需要,因为这不是应该使用 memset 的情况。如果要在字符串上使用 memset,请使用 C 样式字符串。
如果您想深入了解实现细节,MSVC 和 Clang(使用 libc++)使用 string
和短字符串优化,大致如下所示:
class string {
size_t length;
char* ptr;
char short_buf[N];
};
所以如果它是 memset
到 0
,它的析构函数会认为它的长度为零并且可能什么都不做,而且即使它试图 delete[] ptr
,它也赢了不会崩溃,因为 delete
可以很好地处理空指针。
GCC 相反,直到最近才使用完全不同的 string
实现,其中涉及写时复制和引用计数。所以它的内部结构要复杂得多,它在 memset
.
您问的是:
how did it ever work in the first place?
考虑如下实现:
class string
{
public:
string() : size_(0), data_(nullptr) {}
string(char const* s) : size_(strlen(s)), data_(new char(size_+1))
{
strcpy(data_, s);
}
~string()
{
if ( data_ )
{
delete [] data_;
}
}
private:
size_t size_;
char* data_;
};
给定这样的实现,如果 nullptr
由 0
表示(这是最常见的表示),
string s1;
memset(&s1, 0, sizeof(s1)); // Has no impact on s1
string s2("This is a test");
memset(&s2, 0, sizeof(s2)); // Makes s2 the same as a default
// constructed string with memory leak
// as a side effect. Still, it is
// not going to cause segementation
// fault.