Почему мой код C ++ вызывает ошибку сегментации после использования функции read (...)? - PullRequest
1 голос
/ 24 марта 2009

Мое приложение приостанавливается на строке кода, в которой, похоже, нет ничего плохого, однако моя IDE, похоже, приостанавливается на этой строке с ошибкой:

ГБД / миль (24/03/09 13:36) (Выход. Сигнал 'SIGSEGV' получен. Описание: Ошибка сегментации.)

Строка кода просто вызывает метод, в котором нет кода. Не является ли ошибка сегментации, когда у вас есть нулевая ссылка? Если так, то как пустой метод может иметь нулевую ссылку?

Этот фрагмент кода, кажется, вызывает проблему:

#include <sys/socket.h>

#define BUFFER_SIZE 256

char *buffer;

buffer = (char*)GetSomePointer()->SomeStackMemoryString.c_str();
int writeResult = write(socketFD, buffer, BUFFER_SIZE);

bzero(buffer, BUFFER_SIZE);
int readResult = read(socketFD, buffer, BUFFER_SIZE);

Когда закомментирована строка, использующая метод read(...), проблема исчезнет.

Обновление:

Я изменил вопрос, чтобы указать на реальную проблему, и я удалил весь нерелевантный код - и я также ответил на мой собственный вопрос , чтобы люди, читающие это, знали конкретно, в чем проблема, пожалуйста прочитайте мой ответ, прежде чем сказать: «Ты идиот!»

Ответы [ 7 ]

10 голосов
/ 24 марта 2009

Во-первых, вызов метода через нулевой указатель или ссылку строго говоря неопределенное поведение. Но это может произойти, если вызов не является виртуальным.

Вызов виртуальных методов виртуально (через указатель / ссылку, а не из производного класса с способом вызова Class :: Method ()) всегда завершается ошибкой, если ссылка / указатель имеет значение null, поскольку виртуальные вызовы требуют доступа к vtable и доступа к vtable через нулевой указатель / ссылку невозможно. Таким образом, вы не можете вызвать пустой виртуальный метод через ссылку / указатель.

Чтобы понять это, вам нужно больше узнать о том, как организован код. Для каждого не встроенного метода есть сегмент сегмента кода, содержащий машинный код, реализующий метод.

Когда вызов выполняется не виртуально (из производного класса или не виртуального метода через ссылку / указатель), компилятор точно знает, какой метод вызывать (без полиморфизма). Поэтому он просто вставляет вызов в точную часть кода и передает указатель this в качестве первого параметра. В случае вызова через нулевой указатель этот также будет нулевым, но вам все равно, если ваш метод пуст.

Когда вызов выполняется виртуально (через ссылку / указатель), компилятор не знает, какой именно метод вызывать, он только знает, что есть таблица виртуальных методов и адрес таблицы хранится в объекте. Чтобы определить, какой метод вызывать, необходимо сначала разыменовать указатель / ссылку, добраться до таблицы, получить из нее адрес метода и только потом вызывать метод. Чтение таблицы выполняется во время выполнения, а не во время компиляции. Если указатель / ссылка нулевые, вы получаете ошибку сегментации в этой точке.

Это также объясняет, почему виртуальные вызовы не могут быть встроены. Компилятор просто не знает, какой код встроить, когда просматривает исходный код во время компиляции.

3 голосов
/ 24 марта 2009

Ваш код поддельный: буфер указывает на какой-то случайный кусок памяти. Я не уверен, почему строка с bzero не перестает работать.

Правильный код:

   char buffer[BUFFER_SIZE];

   bzero(buffer, BUFFER_SIZE);
   int readResult = read(socketFD, buffer, BUFFER_SIZE);

или вы можете использовать calloc (1, BUFFER_SIZE) для выделения некоторой памяти (и обнуления).

3 голосов
/ 24 марта 2009

Без кода лучшее, что я могу сделать, - это дикое предположение. Но здесь идет:

Ваш "долгосрочный код" пишет в неверный указатель. (Либо полностью случайный указатель, либо проходящий начало / начало буфера или массива). Это происходит из-за того, что таблица виртуальных функций для вашего объекта перекрывается - либо перезаписывает указатель на объект, либо член vptr объекта, либо переписывает фактическую таблицу глобальных виртуальных функций для этого класса.

Некоторые вещи попробовать:

  • Поместите дозорного члена в ваш класс. Например. int, который инициализируется по известному шаблону (0xdeadbeef или 0xcafebabe являются общими) в вашем конструкторе, и никогда не изменяется. Перед вызовом виртуальной функции проверьте (assert ()), что она по-прежнему имеет правильное значение.
  • Попробуйте использовать отладчик памяти. В Linux варианты включают Electric Fence (efence) или Valgrind.
  • Запустите вашу программу под отладчиком (с gdb все в порядке) и поищите, что не так - либо посмертно после segfault, либо устанавливая точку останова непосредственно перед местом, в которое она собирается segfault.
2 голосов
/ 24 марта 2009

Не является ли ошибка сегментации при наличии нулевой ссылки?

Возможно, но не обязательно. Причина, по которой происходит segfault, зависит от платформы, но в основном это означает, что ваша программа обращается к памяти, чего не должно быть. Возможно, вы захотите прочитать статью википедии , чтобы получить лучшее представление о том, что это такое.

Вы можете проверить одну вещь: есть ли у пустого метода тип возвращаемого значения? Я могу ошибаться в этом, но если он возвращает объект, я мог видеть, как конструктор копирования может вызываться на мусоре, если метод фактически не возвращает объект. Это может вызвать все виды шаткого поведения.

Получаете ли вы тот же результат, если вы меняете тип возвращаемого значения на void или возвращаете значение?

2 голосов
/ 24 марта 2009

Я не могу представить себе причину, по которой пустой метод сам по себе мог бы вызвать такую ​​проблему. Без какого-либо другого контекста мое первое, хотя бы, было бы то, что проблема в другом месте повреждает вашу память, и именно здесь это проявляется таким образом.

У нас раньше была такая проблема, и я написал об этом в этом ответе здесь . В этом же вопросе содержится много других полезных советов, которые могут вам помочь.

1 голос
/ 24 марта 2009

Проблема в том, что переменная buffer использует неназначенную память, что вызывает повреждение памяти, когда функция read(...) помещает данные в buffer.

Как правило, bzero фактически вызывает ошибку сегментации, но поскольку в ячейке памяти назначается строка, функции чтения было разрешено записывать после выделенной памяти (вызывая утечку).

/* this causes *some* memory to be allocated, 
 * tricking bzero(...) to not SIGSEGV */
buffer = (char*)GetSomePointer()->SomeStackMemoryString.c_str();

int writeResult = write(socketFD, buffer, BUFFER_SIZE);

Это изменение устраняет утечку памяти:

#define BUFFER_SIZE 256

// Use memory on the stack, for auto allocation and release.
char buffer[BUFFER_SIZE];

// Don't write to the buffer, just pass in the chars on their own.
string writeString = GetSomePointer()->SomeStackMemoryString;
int writeResult = write(socketFD, writeString.c_str(), writeString.length());

// It's now safe to use the buffer, as stack memory is used.
bzero(buffer, BUFFER_SIZE);
int readResult = read(socketFD, buffer, BUFFER_SIZE);
0 голосов
/ 24 марта 2009

Вы вызываете виртуальный метод из конструктора базового класса? Это может быть проблемой: если вы вызываете чистый виртуальный метод из класса Base в конструкторе Base, и он фактически определен только в классе Derived, вы можете получить доступ к записи vtable, которая имеет еще не был установлен, потому что конструктор Derived не был выполнен в этот момент.

...