У меня есть сервер и клиент. Я использую 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 );
}
Я получаю эту ошибку, и я не могу понять, как ее исправить:
Обнаружена критическая ошибка c0000374
server.exe вызвал точку останова.
Я знаю, что есть проблема с указателем и распределением памяти. Но я не доволен c++, и я понятия не имею, что делать.
Самая насущная проблема заключается в том, что вы используете sprintf и sscanf. Избегайте использования sprintf и sscanf - они слишком легки, чтобы случайно создать тип ошибки, которую вы видите здесь, что является переполнением буфера (как на вашем клиенте, так и на вашем сервере).
Рассмотрите, что происходит на вашем клиенте, когда ваше значение "counter" равно 1729. Ваш код будет работать
sprintf(ack, "%d", 1729);
Строковое представление в стиле С 1729 имеет длину в пять байтов - по одному байту для значений символов '1'
, '7'
, '2'
, '9'
и '\0'
. Но ваш буфер ack имеет длину всего 4 байта! Теперь вы написали последний нулевой байт в некоторый фрагмент памяти, который вы никогда не выделяли. В C/C++ это неопределенное поведение, а это значит, что ваша программа может потерпеть крах, а может и не произойти, и если она не сработает, это может закончиться неправильно неправильно позже, или это может работать отлично, или это может работают большую часть времени, за исключением того, что он ломается по вторникам.
Это нехорошее место.
Возможно, вам интересно: "Если это так ужасно, почему sprintf
просто вернул ошибку или что-то, что я назвал ее с слишком маленьким буфером?" Ответ 1 заключается в том, что sprintf
не может выполнить эту проверку, потому что это не дает вам никакого способа сказать, насколько велика ack
самом деле. Когда ваш код здесь вызывает sprintf
, вы знаете, что ack имеет длину 4 байта (поскольку вы только что создали его), но все sprintf видит, является указателем на какую-то память, где-то - вы не сказали, что это длина, поэтому она просто чтобы слепо надеяться, что кусок памяти, который вы ему даете, достаточно велик.
Слепое наделение - довольно плохой способ написать программное обеспечение.
Вот несколько альтернатив, которые вы могли бы рассмотреть здесь.
reinterpret_cast<char*>(&counter)
качестве вашего буфера для отправки 2 с sizeof (counter) в качестве соответствующей длины буфера. Используйте аналогичную конструкцию в recvfrom на другом конце. Обратите внимание, что это будет нарушено, если ваш отправитель и ваш получатель будут иметь разные базовые представления для ints (например, если они используют разные термины), но поскольку вы говорите о Winsock здесь, я предполагаю, что вы предполагаете, что оба конца достаточно недавно версии Windows, где это не будет проблемой.Однако переполнение в sscanf/sprintf не обязательно объясняет это:
Я просто хочу добавить, что ошибка возникает в строке sscanf. Если я прокомментирую эту строку, ошибка встречается в строке recvfrom.
Одним из возможных объяснений этого может быть не предоставление достаточного пространства для удаленного адреса, хотя, пока ваш remote_size
является правильным отражением вашего remote
, я ожидаю, что это заставит recvfrom
вернуть ошибку 3 а не сбой. Другая возможность заключается в передаче плохой памяти/дескрипторов (например, если вы настроили new
оператор, чтобы не отказываться от сбоя, или если инициализация вашего сокета не удалась, и вы не выручили). Невозможно точно сказать, не увидев код, инициализирующий все переменные в игре, и в идеале фактическую ошибку, которую вы получаете в этом сценарии.
1 Несмотря на то, что sprintf не может поймать это, инструменты статического анализа (например, включенные в Visual Studio 2012/2013) очень способны поймать эту конкретную ошибку. Если вы запустите опубликованный код через Visual Studio 2012 Code Analyzer по умолчанию, он будет жаловаться на:
ошибка C4996: 'sprintf': эта функция или переменная может быть небезопасной
2 Некоторые люди предпочитают static_cast<char*>(static_cast<void*>(&counter))
для reinterpret_cast<char*>(&counter)
. Оба работают, это по существу выбор конвенции кодирования.
3 Например, если вы инициализировали remote
SOCKADDR_IN
как SOCKADDR_IN
вместо SOCKADDR_STORAGE
, вы можете столкнуться с такой ошибкой, если вам удастся получить с адреса IPv6. Этот ответ проходит через некоторые из соответствующих подробностей.
char acks[5] = {0};
Кроме того, вы можете показать объявления "remote" и "remove_size"?char* acks = new char[4];
Другие объявления:SOCKADDR_IN remote; int remote_size = sizeof( remote );
И спасибо за помощь.