【问题标题】:c++ winsock2 bad pointers breakpoint triggeredc++ winsock2坏指针断点触发
【发布时间】:2013-11-04 02:15:46
【问题描述】:

我有一个服务器和一个客户端。我正在使用winsock2。客户端发送4个字节:

char *ack = new char[4];
sprintf( ack, "%d", counter );
sendto( clientSocket, ack, 4, 0, ( struct sockaddr* )&remote, sizeof( remote ) );

服务器收到这4个字节:

char* acks = new char[4];
if( ( bytes = recvfrom( serverSocket, acks, 4, 0, ( struct sockaddr* )&remote, &remote_size ) ) == SOCKET_ERROR ) {
    cout << "socket error = " << WSAGetLastError() << endl;
    break;
}
if( bytes > 0 ) {
    sscanf( acks, "%d", &i );
}

我收到了这个错误,我不知道如何解决它:

>Critical error detected c0000374
>
>server.exe has triggered a breakpoint.

我知道指针和内存分配有问题。但我的 c++ 技能是基本的。

【问题讨论】:

  • 如果你确定 char 的大小是 5.. 为什么不直接做 char acks[5] = {0}; 另外,你能显示“remote”和“remove_size”的声明吗?
  • 大小实际上是 4。我尝试了不同的东西,但忘记将代码恢复为正确的形式。数组指针的声明应该是char* acks = new char[4]; 其他声明是:SOCKADDR_IN remote; int remote_size = sizeof( remote ); 并感谢您的帮助。
  • 我实际上编辑了我的原始帖子并修复了 char 数组指针的长度
  • 我只想补充一点,错误发生在 sscanf 行。如果我评论该行,则会在 recvfrom 行中发生错误。这两行中唯一常见的数组指针是acks。

标签: c++ pointers malloc winsock2


【解决方案1】:

字符串格式溢出

最紧迫的问题是您正在使用 sprintf 和 sscanf。避免使用 sprintf 和 sscanf - 它们很容易意外创建您在此处看到的错误类型,即缓冲区溢出(在您的客户端和服务器上)。

考虑当您的“计数器”值为 1729 时,您的客户端会发生什么情况。您的代码将运行

sprintf(ack, "%d", 1729);

1729 的 C 风格字符串表示是五个字节长 - 字符值 '1''7''2''9' 和 @987654330 各一个字节@。但是您的 ack 缓冲区只有 4 个字节长!现在您已将最后一个零字节写入您从未分配的一些内存块中。在 C/C++ 中,这是未定义的行为,这意味着您的程序可能会崩溃,也可能不会,如果它没有崩溃,它可能会在稍后出现微妙的错误,或者它可能会完美运行好吧,或者它可能大部分时间都在工作,除了它在星期二休息。

这不是个好地方。

您可能想知道,“如果这太糟糕了,为什么 sprintf 不只是返回一个错误或我用一个太小的缓冲区调用它的东西?”答案1sprintf 无法进行该检查,因为它无法告诉您ack 实际上有多大。当您在此处的代码调用sprintf 时,知道 ack 的长度为 4 个字节(因为您刚刚创建了它),但是 sprintf 看到的所有内容都是指向某个内存的指针 - 您还没有告诉它一个长度,所以它只能盲目地希望你给它的那块内存足够大。

盲目地希望是一种非常糟糕的软件编写方式。

您可以在这里考虑一些替代方案。

  1. 如果您实际上只是想通过网络发送一个 int,则根本不需要对 int 进行字符串化 - 只需通过将 reinterpret_cast&lt;char*&gt;(&amp;counter) 作为缓冲区传递给 sendto2 以原始格式发送它 以 sizeof(counter) 作为对应的缓冲区长度。在另一端的 recvfrom 中使用类似的结构。请注意,如果您的发送者和接收者具有不同的 int 底层表示(例如,如果它们使用不同的字节序),这将中断,但是由于您在这里谈论的是 Winsock,我假设您假设两端都是最近的不成问题的 Windows 版本。
  2. 如果您确实需要首先对内容进行字符串化,请使用可识别大小的字符串转换函数,例如 boost::format(隐含可识别大小,因为它处理 std::string 而不是原始 char* 缓冲区)或 @ 987654322@/_snscanf_s(显式接受缓冲区长度参数,但特定于 Microsoft)。

Recvfrom 访问冲突

sscanf/sprintf 中的溢出不一定能解释这一点,但是:

我只想补充一点,错误发生在 sscanf 行中。 如果我评论该行,则会在 recvfrom 行中发生错误。

对此的一个可能解释是没有为远程地址提供足够的空间,但只要您的remote_size 是您的remote 的正确反映,我希望这会导致recvfrom 返回一个错误3,不是崩溃。另一种可能性是传递错误的内存/句柄(例如,如果您已将 new 运算符设置为不抛出失败,或者如果您的套接字初始化失败并且您没有退出)。如果没有看到初始化所有变量的代码,以及理想情况下您在这种情况下遇到的实际错误,就不可能准确地说出。


1 尽管 sprintf 无法捕捉到这一点,但静态分析工具(如 Visual Studio 2012/2013 中包含的那些)非常有能力捕捉到这个特定的错误。如果您通过默认的 Visual Studio 2012 代码分析器运行发布的代码,它会报错:

错误 C4996: 'sprintf': 此函数或变量可能不安全

2 有些人更喜欢static_cast&lt;char*&gt;(static_cast&lt;void*&gt;(&amp;counter)) 而不是reinterpret_cast&lt;char*&gt;(&amp;counter)。两者都有效,它本质上是一种编码约定选择。

3 例如,如果您将 remote 初始化为 SOCKADDR_IN 而不是 SOCKADDR_STORAGE,如果您碰巧从 IPv6 地址接收,则可能会遇到此类错误。 This answer 讲述了一些相关的血腥细节。

【讨论】:

  • 非常感谢您提供如此详细的解释。现在它是有道理的。我实际上认为 int 字节存储为字节。所以如果一个整数永远不会超过 32 位 = 4 个字节,我认为长度永远不会溢出。我将 char 数组指针的长度增加到 8,现在可以了。大整数可能会溢出,但我的程序不会使用这么大的整数。现在它工作正常。我急于处理这个项目,所以我没有时间进行更深入的更改。顺便说一句,这个站点被命名为堆栈溢出是有原因的:-)
  • 一个 int(在 Windows 上)确实有 4 个字节长,但是 一个 int 的字符串表示 可以更长(每个数字一个字节加上一个空终止符)。
猜你喜欢
  • 2020-10-04
  • 1970-01-01
  • 1970-01-01
  • 2022-01-18
  • 2011-01-13
  • 1970-01-01
  • 2021-04-16
  • 2018-01-02
  • 1970-01-01
相关资源
最近更新 更多