Программирование на C: ошибки сегментов, printf и связанные с ними причуды - PullRequest
3 голосов
/ 10 июня 2009

Как и многие молодые программисты, я узнал о полезности вставки многочисленных выражений «вывод на консоль» «здесь1», «здесь2» и т. Д. В разные моменты кода, чтобы выяснить, когда мои программы работают неправильно. Эта техника отладки методом грубой силы спасла меня много-много раз за все время обучения CS. Однако когда я начал программировать на C, я наткнулся на интересную проблему. Если бы я попытался запустить

void* test;

printf("hello world");
test[5] = 234;

Конечно, я получаю segfault за недопущение выделения памяти для testChar. Тем не менее, вы могли бы логически подумать, что «hello world» будет напечатан до того, как произойдет ошибка seg, поскольку это поток кода, но, по моему опыту, всегда бывает, что ошибка seg возникает первой, и «hello world» "никогда не выводится на консоль вообще. (Я не смог протестировать этот точный пример, но я сталкивался с подобной ситуацией много раз, используя gcc на Linux-коробке.) Я предполагаю, что это связано либо с компилятором, переставляющим некоторые вещи, и / или printf используя некоторый тип буфера, который очищается асинхронно и поэтому не является немедленным. Это полностью спекуляция с моей стороны, потому что я, честно говоря, не знаю, почему это происходит. На любом другом языке, который я использовал, независимо от того, какую проблему вызывала строка «testChar = ...», «hello world» все равно печатался бы, и, таким образом, я мог определить, где проблема.

Мой вопрос: почему это происходит, когда я программирую на C? Почему привет мир не печатается первым? И во-вторых, есть ли лучшая техника отладки программирования на С, чем эта, которая выполняет ту же самую базовую вещь? Как, например, простой / интуитивно понятный способ найти строку кода, которая является проблемой?

Редактировать: случайно привел рабочий пример, ха-ха. То, что у меня есть сейчас, должно стать причиной ошибки. Забавно, как обычно, когда я не хочу сегфо, я получаю его, а теперь, когда я действительно хотел его, я написал юридический код!

Ответы [ 7 ]

9 голосов
/ 10 июня 2009

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

Редактировать: Вы отредактировали код, чтобы иметь совершенно другое значение. Тем не менее, причина, по которой «hello world» не отображается, заключается в том, что выходной буфер не был очищен. Попробуйте добавить

fflush( stdout );

после печати. ​​

Что касается определения источника проблемы, у вас есть несколько вариантов:

  • свободно распространяет printfs через ваш код с помощью макросов __FILE__ и __LINE__ C
  • научитесь использовать ваш отладчик - если ваша платформа поддерживает дампы ядра, вы можете использовать образ ядра, чтобы найти, где находится ошибка.
5 голосов
/ 10 июня 2009

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

  1. использовать fprintf( stderr, "error string" );, так как stderr не буферизован.
  2. добавить вызов к fflush( stdout ); после вызова printf.

Как сказали Нил и другие, написанный код в порядке. То есть до тех пор, пока вы не начнете изменять буфер, на который указывает testChar.

3 голосов
/ 10 июня 2009

«Как, например, простой / интуитивно понятный способ найти строку кода, которая является проблемой?»

Используйте gdb (или любой другой отладчик).

Чтобы определить, где происходит сбой сегмента вашей программы, вы компилируете ее с опцией -g (для включения символов отладки), запустите ваше приложение из gdb , оно остановится при сбое сегмента.

Затем вы можете посмотреть на трассировку с помощью команды bt, чтобы увидеть, в какой момент вы получили ошибку сегмента.

пример:

> gdb ./x
(gdb) r
Starting program: /proj/cpp/arr/x 
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_PROTECTION_FAILURE at address: 0x00000000
0x000019a9 in willfail () at main.cpp:22
22          *a = 3;
(gdb) bt
#0  0x000019a9 in willfail () at main.cpp:22
#1  0x00001e32 in main () at main.cpp:49
(gdb) 
2 голосов
/ 10 июня 2009

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

fprintf(stderr, "hello, world\n");

(по умолчанию stderr небуферизован.)

1 голос
/ 10 июня 2009

Этот код не должен содержать ошибки. Вы просто назначаете указатель на литеральную строку переменной указателя. Все было бы иначе, если бы вы были, например. используя strcpy для копирования содержимого с неверным указателем.

Сообщение не появляется, возможно, из-за буферизованного ввода-вывода. Выведите символ новой строки \n или вызовите fflush, чтобы очистить буфер вывода.

0 голосов
/ 10 июня 2009
void* test;

printf("hello world");
test[5] = 234;

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

0 голосов
/ 10 июня 2009

У вас две проблемы. Во-первых, ваш (оригинальный) код не будет работать по умолчанию. Совершенно верно присвоить эту строковую константу указателю на символ. Но давайте пока оставим это в стороне и представим, что вы положили туда что-то, что будет сегфо.

Тогда обычно речь идет о буферах: один в библиотеке времени выполнения C и один в самой ОС. Вы должны очистить их.

Самый простой способ сделать это (в UNIX не совсем уверен насчет fsync в Linux, но вы должны быть уверены, что это произойдет в конце концов, если сама система не выйдет из строя):

printf ("DEBUG point 72\n"); fflush (stdout); fsync (fileno (stdout));

Я часто делал это в UNIX, и это гарантирует, что библиотеки времени выполнения C сбрасываются в UNIX (fflush), а буферы UNIX синхронизируются на диск (fsync), полезно, если stdout не является терминальное устройство или вы делаете это для другого дескриптора файла.

...