Странная ошибка в C - PullRequest
       13

Странная ошибка в C

0 голосов
/ 22 августа 2009

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

Я установил указатель на NULL. Если в той же функции, в которой я устанавливаю указатель на NULL, я printf() указатель (с "%p"), я получаю 0x0, и когда я печатаю тот же указатель за миллион миль в конце моя программа, я получаю 0x0. Если я удалю printf() и внесу абсолютно никаких других изменений , то, когда указатель будет напечатан позже, я получу 0x1, и другие случайные переменные в моей структуре также будут иметь неверные значения. Я компилирую его с GCC на -O2, но он ведет себя так же, если я отключаю оптимизацию, так что это не проблема.

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

РЕДАКТИРОВАТЬ: Каким-то образом это работает сейчас. Спасибо всем вам за ваши предложения.

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

Ответы [ 5 ]

8 голосов
/ 22 августа 2009

Используете ли вы несколько потоков? Я часто обнаруживал, что распечатки чего-либо может быть достаточно, чтобы эффективно подавить состояние гонки (т.е. не устранять ошибку, а только затруднить ее обнаружение).

Что касается того, как его диагностировать / исправить ... Вы можете переместить второй отпечаток раньше и раньше, пока не увидите, где он меняется?

Вы всегда видите 0x1 позже, когда у вас нет printf там?

Один из способов избежать задержки / синхронизации printf состоит в том, чтобы скопировать значение указателя в другую переменную в месте первого printf, а затем распечатать это значение - чтобы вы могли увидеть, какое значение был в тот момент, но в менее критичной ко времени точке. Конечно, поскольку у вас происходит странное значение «коррупции», это может быть не так надежно, как кажется ...

РЕДАКТИРОВАТЬ: Тот факт, что вы всегда видите 0x1 обнадеживает. Это должно облегчить поиск. По общему признанию, отсутствие многопоточности затрудняет объяснение.

Интересно, связано ли это с дополнительным вызовом printf с изменением размера стека? Что произойдет, если вы напечатаете значение другой переменной в том же месте, где был первый вызов printf?

РЕДАКТИРОВАТЬ: Хорошо, давайте продолжим идею стека. Можете ли вы создать другую функцию с такой же сигнатурой, как у printf и с достаточным количеством кода, чтобы избежать ее вставки, но которая на самом деле ничего не печатает? Назовите это вместо printf и посмотрите, что произойдет. Я подозреваю, что ты все еще будешь в порядке.

В принципе, я подозреваю, что вы где-то портили свою стековую память, например, записывая за конец массива в стеке; изменение способа использования стека путем вызова функции может маскировать ее.

4 голосов
/ 22 августа 2009

Если вы работаете на процессоре, который поддерживает аппаратные точки прерывания данных (например, x86), просто установите точку останова при записи в указатель.

1 голос
/ 22 августа 2009

У вас есть отладчик? Если так, то как значения выглядят в этом? Вы можете установить какой-либо тип памяти / аппаратную точку останова на значение? Может быть, что-то попирает память в другом месте, и printf перемещает вещи настолько, чтобы переместить или скрыть ошибку?

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

0 голосов
/ 22 августа 2009

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

Вот мое первое предложение: используйте отладчик и установите точку наблюдения в этом месте указателя.

Если это невозможно или ошибка снова исчезает, вот мое второе предложение.

1 / Начните с ошибочного кода, где вы печатаете значение указателя и видите 0x1.

2 / Вставить еще один printf немного назад (с точки зрения пути выполнения кода).

3 / Если он все еще равен 0x1, вернитесь к шагу 2, каждый раз немного возвращаясь назад по пути выполнения.

4 / Если это 0x0, вы знаете, в чем проблема.

Если между 0x0 printf и 0x1 printf нет ничего очевидного, вероятно, это какая-то коррупция. Без точки наблюдения это будет трудно отследить - вам нужно проверить каждую переменную стека, чтобы убедиться в отсутствии возможности переполнения.

Я предполагаю, что указатель является глобальным, поскольку вы устанавливаете его и печатаете его «за миллион миль». Если это так, посмотрите на переменные, которые вы определяете по обе стороны от него (в источнике). Они наиболее вероятно вызывают переполнение.

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

0 голосов
/ 22 августа 2009

Вы пытались установить условие в своем отладчике, которое уведомляет вас, когда это значение изменяется? Или запустить его через Valgrind? Это две основные вещи, которые я бы попробовал, особенно Valgrind, если вы используете Linux. Нет лучшего способа выяснить ошибки памяти.

...