Программирование на C: ошибки сегментов, printf и связанные с ними причуды

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

void* test;

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

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

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

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

7 ответов

Решение

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

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

fflush( stdout );

после печати

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

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

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

Используйте 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) 

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

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

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

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

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

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

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

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

void* test;

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

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

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

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

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

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

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

Другие вопросы по тегам