C ++ Winsock2 плохие указатели сработали точки останова

0

У меня есть сервер и клиент. Я использую 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++, и я понятия не имею, что делать.

  • 0
    Если вы точно знаете, что char имеет размер 5, почему бы просто не сделать char acks[5] = {0}; Кроме того, вы можете показать объявления "remote" и "remove_size"?
  • 0
    Размер на самом деле 4. Я пробовал разные вещи, и я забыл вернуть код в нужную форму. Объявление указателя массива должно быть char* acks = new char[4]; Другие объявления: SOCKADDR_IN remote; int remote_size = sizeof( remote ); И спасибо за помощь.
Показать ещё 2 комментария
Теги:
pointers
malloc
winsock2

1 ответ

0
Лучший ответ

Переполнение форматирования строк

Самая насущная проблема заключается в том, что вы используете 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 видит, является указателем на какую-то память, где-то - вы не сказали, что это длина, поэтому она просто чтобы слепо надеяться, что кусок памяти, который вы ему даете, достаточно велик.

Слепое наделение - довольно плохой способ написать программное обеспечение.

Вот несколько альтернатив, которые вы могли бы рассмотреть здесь.

  1. Если вы на самом деле просто пытаетесь отправить int через провод, на самом деле нет никакой необходимости в создании строки int - просто отправьте его в свой собственный формат, передав reinterpret_cast<char*>(&counter) качестве вашего буфера для отправки 2 с sizeof (counter) в качестве соответствующей длины буфера. Используйте аналогичную конструкцию в recvfrom на другом конце. Обратите внимание, что это будет нарушено, если ваш отправитель и ваш получатель будут иметь разные базовые представления для ints (например, если они используют разные термины), но поскольку вы говорите о Winsock здесь, я предполагаю, что вы предполагаете, что оба конца достаточно недавно версии Windows, где это не будет проблемой.
  2. Если вам действительно нужно сначала скомпоновать содержимое, используйте функции преобразования строк в виде размера, такие как boost :: format (который неявно зависит от размера, потому что он имеет дело с std :: string вместо raw char * buffers) или _snprintf_s/_snscanf_s (которые явно принимают параметры длины буфера, но специфичны для Microsoft).

Нарушение прав доступа

Однако переполнение в 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. Этот ответ проходит через некоторые из соответствующих подробностей.

  • 0
    Большое спасибо за такое подробное объяснение. Теперь это имеет смысл. Я действительно думал, что байты int хранятся как байты. Поэтому, если целое число никогда не превышает 32 бита = 4 байта, я думал, что длина никогда не будет переполнена. Я увеличил длину указателя массива char до 8, и теперь все в порядке. Это может переполниться для больших целых чисел, но моя программа не будет использовать такие большие целые числа. Теперь работает нормально. Я спешу передать этот проект, поэтому у меня нет времени, чтобы сделать более глубокие изменения. Кстати, есть причина, по которой этот сайт называется переполнением стека :-)
  • 0
    Int (в Windows) действительно имеет длину 4 байта, но строковое представление int может быть намного длиннее (байт на цифру плюс нулевой терминатор).

Ещё вопросы

Сообщество Overcoder
Наверх
Меню