未初始化的字节指针问题:Valgrind 错误

10

我一直在使用Valgrind来查找代码中的内存泄漏问题,尽管没有发现任何内存泄漏,但是一些错误报告都源自于单个函数/类方法:

==17043== ERROR SUMMARY: 10100 errors from 3 contexts (suppressed: 0 from 0)
==17043== 
==17043== 100 errors in context 1 of 3:
==17043== Syscall param socketcall.sendto(msg) points to uninitialised byte(s)
==17043==    at 0x5441DA2: send (send.c:28)
==17043==    by 0x404C2D: unix_socket::sendMsg(char, double) (in /home/joao/CloudPT/Bolsa/Webots/controllers/darwin-pi2/client)
==17043==    by 0x404F1C: unix_socket::sendVectorXd(Eigen::Matrix<double, -1, 1, 0, -1, 1> const&) (in /home/joao/CloudPT/Bolsa/Webots/controllers/darwin-pi2/client)
==17043==    by 0x401F2A: main (in /home/joao/CloudPT/Bolsa/Webots/controllers/darwin-pi2/client)
==17043==  Address 0x7feffff61 is on thread 1's stack
==17043==  Uninitialised value was created by a stack allocation
==17043==    at 0x404BE6: unix_socket::sendMsg(char, double) (in /home/joao/CloudPT/Bolsa/Webots/controllers/darwin-pi2/client)
==17043== 
==17043== 
==17043== 100 errors in context 2 of 3:
==17043== Syscall param socketcall.sendto(msg) points to uninitialised byte(s)
==17043==    at 0x5441DA2: send (send.c:28)
==17043==    by 0x404C2D: unix_socket::sendMsg(char, double) (in /home/joao/CloudPT/Bolsa/Webots/controllers/darwin-pi2/client)
==17043==    by 0x404E8A: unix_socket::sendVectorXd(Eigen::Matrix<double, -1, 1, 0, -1, 1> const&) (in /home/joao/CloudPT/Bolsa/Webots/controllers/darwin-pi2/client)
==17043==    by 0x401F2A: main (in /home/joao/CloudPT/Bolsa/Webots/controllers/darwin-pi2/client)
==17043==  Address 0x7feffff61 is on thread 1's stack
==17043==  Uninitialised value was created by a stack allocation
==17043==    at 0x404BE6: unix_socket::sendMsg(char, double) (in /home/joao/CloudPT/Bolsa/Webots/controllers/darwin-pi2/client)
==17043== 
==17043== 
==17043== 9900 errors in context 3 of 3:
==17043== Syscall param socketcall.sendto(msg) points to uninitialised byte(s)
==17043==    at 0x5441DA2: send (send.c:28)
==17043==    by 0x404C2D: unix_socket::sendMsg(char, double) (in /home/joao/CloudPT/Bolsa/Webots/controllers/darwin-pi2/client)
==17043==    by 0x404EE8: unix_socket::sendVectorXd(Eigen::Matrix<double, -1, 1, 0, -1, 1> const&) (in /home/joao/CloudPT/Bolsa/Webots/controllers/darwin-pi2/client)
==17043==    by 0x401F2A: main (in /home/joao/CloudPT/Bolsa/Webots/controllers/darwin-pi2/client)
==17043==  Address 0x7feffff61 is on thread 1's stack
==17043==  Uninitialised value was created by a stack allocation
==17043==    at 0x404BE6: unix_socket::sendMsg(char, double) (in /home/joao/CloudPT/Bolsa/Webots/controllers/darwin-pi2/client)
==17043== 
==17043== ERROR SUMMARY: 10100 errors from 3 contexts (suppressed: 0 from 0)

sendMsg(const char _type, const double _value) 是被指出错误的部分,属于 unix_socket 类。

//...
typedef struct{
    char type;    
    double value; 
} MESSAGE;

//...
int unix_socket::sendMsg(const char _type, const double _value){
    MESSAGE msg;
    msg.type=_type;
    msg.value=_value;
    int n = send(client_sock, &msg, sizeof(msg), 0);
    if (n < 0) {
        perror("send");
        return -1;
    } 
    c_sent=msg.type;
    v_sent=msg.value;
    return 0;
}

我不明白问题出在哪里。未初始化的值具体在哪里?或者我应该忽略Valgrind报告的错误?

2个回答

16

看看 MESSAGE 结构体:

typedef struct{
    char type;    
    double value; 
} MESSAGE;

由于数据结构对齐的缘故,value 的地址可能会被强制对齐到一个字大小的地址上。因此,在 MESSAGE::typeMESSAGE::value 之间会填充几个未使用的字节。这些字节没有被初始化,因此被 Valgrind 报告。

作为解决方法,您可以通过使用 memset() 强制初始化整个结构体。

MESSAGE msg;
memset(&msg, 0, sizeof(MESSAGE));
msg.type=_type;
msg.value=_value;

1
另一种选择是将结构打包成字节向量并发送,缩小到每个元素的确切大小。在结构形式下通过线路发送结构永远不是一个好主意。客户端读取当前机制时无法知道结构填充是什么,因此无法知道value是否对齐于1字节、2字节、4字节甚至8字节。如果我要编写这个代码,我可能会使用长度明确的协议,并具有打包和解包代码以确保值是正确的,从而消除对齐问题。 - WhozCraig
你说得对,那个方法可行!但是,如果保持原样,我可能会遇到什么问题? - joaocandre
2
只要它们都使用相同的结构打包设置进行编译,或者将此特定结构#pragma为单字节打包,那么不,你就不会有问题。但是想象一下,如果一端使用4字节对齐方式,而另一端使用单字节对齐方式。一端将期望该结构为12个字节,而另一端将期望9个字节。显然这是行不通的。如果你完全控制了两端,只要每个人遵守相同的规则,你现在就可以使用它。我上面描述的就是我如何操纵游戏以确保它能够正常工作,无论规则如何。 - WhozCraig
@joaocandre 注意:这是目前为止最好的答案,如果它有帮助,请给它点赞并标记(勾选)为正确答案。 - WhozCraig
当客户端和服务器使用不同的字节对齐方式时,这将会失败。 - John Dibling
显示剩余3条评论

11

虽然 @timrau 已经相当准确地描述了这里的核心问题 (对齐/打包), 但是我不喜欢提出的解决方案。

你在代码中将一个 MESSAGE 描述为由一个 char 和一个 double 组成。然而,实际内存中的数据结构大小并不是 sizeof(char) + sizeof(double),这才是核心问题所在。

提出的解决方案建议在填充重要部分之前简单地清除 MESSAGE 结构的所有位。我对此的问题既有语义问题,也有技术问题——发送到网络的数据结构的大小不是代码中建模的准确表示。换句话说,你不仅仅是发送一个 char 和一个 double——你还发送一些其他的废物(填充)。

我的建议是摆脱这些废物,只发送你在代码中建模的东西。

C++ 中没有直接支持关闭对齐和填充的机制,但是我知道的所有编译器都提供了一种简单的机制,可以将数据结构对齐到 N 字节:

#pragma pack (push, 1)

typedef struct{
    char type;    
    double value; 
} MESSAGE;

#pragma pack (pop)

这将使MESSAGE数据结构完全与您在代码中建模的相同,没有填充。这样做使得memset变得不必要,并且您将会向以下发送sizeof(char) + sizeof(double)字节。


这可能是一个基础问题,但即使这个结构体被打包到1字节对齐,接收端(套接字的另一侧)如何知道呢? - joaocandre
如果我必须快速完成这个任务,我会采取这种方法。如果我必须以可移植的方式完成它,我可能会花时间序列化成员。但是这仍然值得我的点赞,并且已经得到了相应的认可。从我所看到的所有内容来看,这已经足够满足原始问题的要求。 - WhozCraig
@WhozCraig:可移植性是主观的。我在许多平台上做了很多年这个工作。 - John Dibling
当然,这是主观陈述,并且我毫不怀疑您不会在 sizeof(double) 不相同的平台之间执行此操作。 - WhozCraig
很难不同意这一点。我也是这个阵营的。 - WhozCraig
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接