关于c ++:Addressof运算符在嵌入式环境中返回无效地址


Addressof operator returns invalid address in embedded environment

在一个交叉编译的嵌入式Linux环境中调试segfault错误时,我将问题隔离为memset调用。

进一步的调查表明,发生了一些奇怪的事情。我尝试使用以下代码在目标上运行测试应用程序:

1
2
3
4
5
6
7
string st;
st ="teststring";
std::cout <<"address:" << &st << std::endl;
std::cout <<"sizeof:" << sizeof(st) << std::endl;
std::cout <<"value (before):" << st << std::endl;
memset(&st,0,sizeof(st));
std::cout <<"value (after):" << st << std::endl;

应用程序在memset行上退出,并出现segfault。输出是:

1
2
3
4
5
address: 0xbed7fb4c
sizeof: 4
value (before): teststring
Segmentation fault (core dumped)
Application finished with exit code 139.

在桌面环境中编译和运行的相同代码生成以下输出:

1
2
3
4
address: 0x7ffdc172f7a0
sizeof: 32
value (before): teststring
value (after):

为什么相同的代码在桌面和嵌入式系统上的行为不同?

这两种都是不使用qt组件的qt应用程序。编译器是桌面的gcc和嵌入式系统的buildRootgcc。

memset本身在这里不是问题(由不同的sizeof结果指示)。以下代码还会在嵌入式系统上生成一个segfault:

1
2
3
4
5
6
string st;
st ="teststring";
char* p = (char*)&st;
for (size_t i = 0; i != sizeof(st); ++i) {
        p[i] = 0;
}


std::memset要求传递给它的对象是可复制的。std::string不是一般可复制的,因此在它上面调用memset是未定义的行为。

如果要清除string的内容,则应在实例上调用clear

第二个示例也是非法的,因为您违反了严格的别名规则。std::string不是C字符串。不能将字符串对象的地址视为基础C字符串中的第一个字符。尽管如此,字符串的大小首先存储在类中,然后用垃圾覆盖它。


你试图执行非法操作。

要在对象上使用std::memset,它必须是一个聚合。std::string不是一个。如果要用零填充,请使用std::fill。如果你只是想把它清空,就用clear()

如果你真的,真的想以字符数组的形式访问字符串,你可以说

1
2
3
4
char* p = &st[0];
for (size_t i = 0; i != st.size(); ++i) {
        p[i] = 0;
}

&st[0]生成第一个字符的地址,字符串保证数据的连续内存布局。


为了增加给出的答案,如果你想确保你不创建这样的程序(比如调用EDOCX1[2]),使用类型特征和std::是非常容易复制的

1
2
3
4
5
6
7
8
#include <type_traits>
#include <string>

int main()
{
   static_assert(std::is_trivially_copyable<std::string>(),
                "Sorry, you're memset is not going to work");
}

现在,由于给定的std::is_trivially_copyable类型未能通过静态断言,程序将无法编译,更不用说运行了。

与此相比:

1
2
3
4
5
6
7
8
9
10
11
12
#include <type_traits>

struct foo
{
    char x[10];
};

int main()
{
   static_assert(std::is_trivially_copyable<foo>(),
                "Sorry, you're memset is not going to work");
}

编译时没有错误,因为foo是可复制的。类型特征是你在这里的朋友。