8 Compilation and run time errors
The R interface to the debugger (gdb) is documented as part of the R help system, i.e. you can type ?gdbsource in R to get info. The current document only adresses isses that the relate to C++.
8.1 Compilation errors
It may be hard to understand the compilation errors for the following reasons
- The Eigen libraries use templated C++ which generate non-intuitive error messages.
8.2 Run time errors
Run time errors are broadly speaking of two types:
- Out-of-bounds (you are “walking out of an array”)
- Floating point exceptions
You can use the debugger to locate both types of errors, but the procedure is a little bit different in the two cases. The following assumes that you have the GNU debugger gdb installed.
8.2.1 Out-of-bounds error
This will cause TMB and R to crash with the following error message:
TMB has received an error from Eigen. The following condition was not met: index >= 0 && index < size() Please check your matrix-vector bounds etc., or run your program through a debugger. Aborted (core dumped)
So, you must restart R and give the commands
#5 objective_function ::operator() (this= ) at nan_error_ex.cpp:11
and you can see that the debugger points to line number 11 in the .cpp file. gdbsource() is an R function that is part of TMB.
8.2.2 Floating point exception
If you on the other hand perform an illegal mathematical operation, such as
R will not crash, but the objective function will return a NaN value. However, you will not know in which part of your C++ code the error occured. By including the fenv.h library (part of many C++ compilers, but can otherwise be downloaded from http://www.scs.stanford.edu/histar/src/uinc/fenv.h)
a floating point exception will be turned into an actual error that can be picked up by the debugger. There are only two extra lines that need to be included (“//Extra line needed” in the above example).
When we try to run this program in the usual way, the program crashes:
Floating point exception (core dumped) tmp3>
At this stage you should run the debugger to find out that the floating point exception occurs at line number 14:
#1 0x00007ffff0e7eb09 in objective_function ::operator() (this= ) at nan_error_ex.cpp:14
This enabling of floating point errors applies to R as well as the TMB program. For more elaborate R-scripts it may therefore happen that a NaN occurs in the R-script before the floating point exception in the TMB program (i.e. the problem of interest) happens. To circumvent this problem one can run without NaN debugging enabled and save the parameter vector that gave the floating point exception (e.g. badpar <- obj$env$last.par after the NaN evaluation), then enable NaN debugging, re-compile, and evaluate obj$env$f( badpar, type=»double») .
8.2.3 Missing casts for vectorized functions
TMB vectorized functions cannot be called directly with expressions, for example the following will fail to compile:
error: could not convert ‘atomic::D_lgamma(const CppAD::vector &) … from ‘double’ to ‘Eigen::CwiseBinaryOp<Eigen::internal::scalar_sum_op<double, double>, … >’
Eigen lazy-evaluates expressions, and the templating of lgamma means we expect to return a “x + y”-typed object, which it obviously can’t do.
Неопределённое поведение и правда не определено
Термином «неопределённое поведение» в языке C и C++ обозначают ситуацию, в которой буквально «чего только не бывает». Исторически, к неопределённому поведению относили случаи, когда прежние компиляторы для C (и архитектуры на нём) вели себя несовместимым образом, и комитет по разработке стандарта, в своей безграничной мудрости, решил ничего не решать по этому поводу (т.е. не отдавать предпочтение какой-то одной из конкурирующих реализаций). Неопределённым поведением также называли возможные ситуации, в которых стандарт, обычно столь исчерпывающий, не предписывал никакого конкретного поведения. У этого термина есть и третье значение, которое в наше время становится всё более актуальным: неопределённое поведение — это возможности для оптимизации. А разработчики на C и C++ обожают оптимизации; они настойчиво требуют, чтобы компиляторы прикладывали все усилия для ускорения работы кода.
Данная статья была впервые опубликована на сайте Cryptography Services. Перевод публикуется с разрешения автора Томаса Порнина (Thomas Pornin).
Вот классический пример:
Скомпилируем этот код GCC на 64-битной x86-платформе под Linux (я работаю на свежей версии Ubuntu 18.04, версия GCC — 7.3.0). Включим полную оптимизацию, а затем посмотрим на листинг ассемблера, для чего задействуем ключи «-W -Wall -O9 -S» (аргументом «-O9» задаётся максимальный уровень оптимизации GCC, который на практике эквивалентен «-O3«, хотя в некоторых форках GCC определены и более высокие уровни). Получаем следующий результат:
Каждая из первых двух инструкций movupd перемещает два значения типа double в 128-битный регистр SSE2 (double имеет размер 64 бита, поэтому регистр SSE2 может хранить два значения типа double). Другими словами, сначала считываются четыре исходных значения, а уже потом они приводятся к int (операции cvttpd2dq). Операция punpcklqdq перемещает четыре полученных 32-битных целых значения в один регистр SSE2 (%xmm0), содержимое которого затем записывается в оперативную память (movups). А теперь главное: наша C-программа формально требует, чтобы доступ к памяти происходил в следующем порядке:
- Считать первое значение типа double из src[0].
- Записать первое значение типа int в dst[0].
- Считать второе значение типа double из src[1].
- Записать второе значение типа int в dst[1].
- Считать третье значение типа double из src[2].
- Записать третье значение типа int в dst[2].
- Считать четвёртое значение типа double из src[3].
- Записать четвёртое значение типа int в dst[3].
- Считать первое значение типа double из src[0].
- Считать второе значение типа double из src[1].
- Считать третье значение типа double из src[2].
- Считать четвёртое значение типа double из src[3].
- Записать первое значение типа int в dst[0].
- Записать второе значение типа int в dst[1].
- Записать третье значение типа int в dst[2].
- Записать четвёртое значение типа int в dst[3].
Вот тут, к счастью, на помощь и приходит неопределённое поведение. Стандарт C гласит, что доступ к значениям «не может» быть осуществлён через указатели, тип которых не соответствует текущим типам этих значений. Проще говоря, если значение записывается в dst[0], где dst -указатель типа int, то соответствующие байты не могут быть считаны через src[1], где src — указатель типа double, так как в этом случае мы попытались бы получить доступ к значению, которое теперь имеет тип int, с помощью указателя несовместимого типа. В этом случае возникло бы неопределённое поведение. Об этом говорится в параграфе 7 раздела 6.5 стандарта ISO 9899:1999 («C99») (в новой редакции 9899:2018, или «C17», формулировка не изменилась). Это предписание называется правилом строгого алиасинга(strict aliasing). Как следствие, компилятору C разрешено действовать, исходя из предположения, что операции доступа к памяти, приводящие к неопределённому поведению из-за нарушения правила строгого алиасинга, не происходят. Таким образом, компилятор может переставлять операции чтения и записи в любом порядке, поскольку они не должны обращаться к перекрывающимся участкам памяти. В этом и состоит оптимизация кода.
Смысл неопределённого поведения, если кратко, заключается в следующем: компилятор может допустить, что неопределённого поведения не будет, и сгенерировать код, исходя из этого допущения. В случае правила строгого алиасинга — при условии, что алиасинг имеет место быть, — неопределённое поведение позволяет проводить важные оптимизации, реализовать которые иначе было бы сложно. Если говорить в целом, каждая инструкция в процедурах генерации кода, используемых компилятором, имеет зависимости, ограничивающие алгоритм планирования операций: инструкция не может быть выполнена раньше тех инструкций, от которых она зависит, или после тех инструкций, которые зависят от неё. В нашем примере неопределённое поведение устраняет зависимости между операциями записи в dst[] и «последующими» операциями чтения из src[]: такая зависимость может существовать только в тех случаях, когда при доступе к памяти возникает неопределённое поведение. Точно также понятие неопределённого поведения позволяет компилятору просто удалять код, который не может быть выполнен без вхождения в состояние неопределённого поведения.
Всё это, конечно, хорошо, но такое поведение иногда воспринимается как вероломное предательство со стороны компилятора. Можно часто услышать такую фразу: «Компилятор использует понятие неопределённого поведения как предлог сломать мне код». Допустим, некто пишет программу, которая складывает целые числа, и опасается переполнения — вспомним случай с Bitcoin. Он может размышлять так: для представления целых чисел процессор использует дополнительный код, а значит если переполнение случится, то случится оно потому, что результат будет усечён до размера типа, т.е. 32 бит. Значит, результат переполнения можно предсказать и проверить тестом.
Наш условный разработчик напишет так:
Теперь попробуем скомпилировать этот код с помощью GCC:
Хорошо, вроде работает. Теперь попробуем другой компилятор, например, Clang (у меня версия 6.0.0):
Выходит, что, когда операция со знаковыми целыми типами приводит к результату, который не может быть представлен целевым типом, мы вступаем на территорию неопределённого поведения. Но ведь компилятор может допустить, что оно не происходит. В частности, оптимизируя выражение x > 0 && y > 0 && r < x, компилятор делает вывод, что раз значения x и y строго положительные, то третья проверка не может быть истинной (сумма двух значений не может быть меньше любого из них), и всю эту операцию можно пропустить. Другими словами, так как переполнение — это неопределённое поведение, оно «не может произойти» с точки зрения компилятора, и все инструкции, которые зависят от этого состояния, можно удалить. Механизм обнаружения неопределённого поведения попросту исчез.
Стандарт никогда не предписывал допускать, что в вычислениях со знаковыми типами используется «семантика заворачивания» (которая действительно используется в операциях процессора); так сложилось скорее в силу традиции — ещё в те времена, когда компиляторы не были достаточно сообразительны, чтобы оптимизировать код, ориентируясь на диапазон значений. Заставить Clang и GCC применять семантику заворачивания к знаковым типам можно через специальный флаг -fwrapv (в Microsoft Visual C для этого можно использовать -d2UndefIntOverflow-, как описано здесь). Однако этот подход ненадёжен, флаг может исчезнуть при переносе кода в другой проект или на другую архитектуру.
Мало кто знает о том, что переполнение знаковых типов подразумевает неопределённое поведение. Про это сказано в параграфе 5 раздела 6.5 стандартов C99 и C17:
Если при вычислении выражения возникает исключительное состояние (т.е. если результат математически не определён или находится вне диапазона допустимых значений данного типа), поведение не определено.
Для беззнаковых типов, однако, гарантирована модульная семантика. В параграфе 9 раздела 6.2.5 сказано следующее:
Переполнение никогда не возникает при вычислениях с беззнаковыми операндами, так как результат, который не может быть представлен результирующим беззнаковым целым типом, усекается по модулю числа, которое на единицу больше максимального значения, представляемого результирующим типом.
Ещё одним примером неопределённого поведения в операциях со знаковыми типами является операция деления. Как всем известно, результат деления на ноль математически не определён, поэтому, согласно стандарту, эта операция влечёт за собой неопределённое поведение. Если в операции idiv на x86-процессоре делитель равен нулю, возникает исключение процессора. Подобно запросам на прерывание, исключения процессора обрабатываются операционной системой. На Unix-подобных системах, например Linux, исключение процессора, спровоцированное операцией idiv, переводится в сигнал SIGFPE, который посылается процессу, и тот завершается обработчиком по умолчанию (не удивляйтесь, что «FPE» расшифровывается как «floating-point exception» (исключение в операции с плавающей запятой), тогда как idiv работает с целыми числами). Но есть ещё одна ситуация, которая приводит к неопределённому поведению. Рассмотрим следующий код:
И правда: на этой машине (всё та же x86 под Linux) тип int представляет диапазон значений от -2 147 483 648 до +2 147 483 647. Если разделить -2 147 483 648 на -1, должно получиться +2 147 483 648. Но это число не входит в диапазон значений типа int. Поэтому поведение не определено. Может случиться всё что угодно. В данном случае процесс принудительно завершается. На другой системе, особенно с небольшим процессором, в котором нет операции деления, результат может отличаться. В таких архитектурах деление выполняется программно — с помощью процедуры, обычно предоставляемой компилятором, и вот она может сделать с неопределённым поведением всё, что ей заблагорассудится, ведь именно в этом оно и заключается.
Замечу, что SIGFPE можно получить при тех же условиях и с помощью оператора деления по модулю (%). И в самом деле: под ним скрывается всё та же операция idiv, которая вычисляет и частное, и остаток, поэтому срабатывает то же самое исключение процессора. Что интересно, стандарт C99 говорит, что выражение INT_MIN % -1 не может приводить к неопределённому поведению, поскольку результат математически определён (ноль) и однозначно входит в диапазон значений целевого типа. В версии C17 текст параграфа 6 раздела 6.5.5 был изменён, и теперь этот случай также учитывается, что приближает стандарт к реальному положению дел на распространённых аппаратных платформах.
Существует множество неочевидных ситуаций, также приводящих к неопределённому поведению. Взгляните на этот код:
Как думаете, что программа, следуя стандарту C, должна распечатать, если в функцию передать множители 45 000 и 50 000?
- 18 048
- 2 250 000 000
- Боже, храни Королеву!
Однако на практике, с обычными компиляторами, получится значение 18 048, поскольку не известно ещё такой оптимизации, которая смогла бы воспользоваться неопределённым поведением в данной конкретной программе (можно вообразить более искусственные сценарии, где оно действительно натворило бы бед).
Напоследок ещё один пример, теперь уже на C++:
Это вам не типичный «плохой ужасный strcpy()!». Ведь здесь функция strcpy() выполняется, только если размер исходной строки, включая терминальный ноль, достаточно мал. Более того, элементы массива явно инициализируются нулём, поэтому все байты в массиве имеют заданное значение независимо от того, большая или маленькая строка передаётся в функцию. Вместе с тем, цикл в конце некорректен: он считывает на один байт больше, чем положено.
Вы можете наивно возразить: ну хорошо, он считывает лишний байт за границей массива; но это не так уж и страшно, ведь на стеке этот байт всё равно есть, в память отображается, так что единственная проблема тут — это лишний семнадцатый элемент с неизвестным значением. Цикл всё равно напечатает ровно 17 целых чисел (в шестнадцатеричном формате) и завершится без нареканий.
Но у компилятора своё мнение на этот счёт. Он прекрасно понимает, что семнадцатое считывание провоцирует неопределённое поведение. По его логике, любая последующая инструкция оказывается в подвешенном состоянии: нет такого требования, что после неопределённого поведения что-то вообще должно существовать (формально даже предыдущие инструкции могут оказаться под ударом, так как неопределённое поведение действует и в обратную сторону). В нашем случае компилятор просто проигнорирует проверку условия в цикле, и тот будет крутиться вечно, точнее, до тех пор, пока не начнёт читать за пределами выделенной под стек памяти, после чего сработает сигнал SIGSEGV.
Забавно, но если GCC запустить с менее агрессивными настройками для оптимизаций, он выдаст предупреждение:
На уровне -O9 это предупреждение почему-то исчезает. Возможно, дело в том, что на высоких уровнях оптимизации компилятор более агрессивно навязывает развёртывание цикла. Возможно (но неточно), что это баг GCC (в смысле пропажа предупреждения; так-то действия GCC в любом случае не противоречат стандарту, ведь тот не требует выдачи «диагностик» в такой ситуации).
Вывод: если вы пишете код на C или C++, будьте предельно внимательны и избегайте ситуаций, приводящих к неопределённому поведению, даже когда кажется, что «ничего страшного».
Беззнаковые целочисленные типы — хороший помощник в арифметических вычислениях, поскольку для них гарантирована модульная семантика (но всё равно можно получить проблемы, связанные с расширением целых типов). Ещё один вариант — почему-то непопулярный — вовсе не писать на C и C++. По ряду причин это решение не всегда подходит. Но если есть возможность выбрать, на каком языке писать программу, т.е. когда вы только приступаете к новому проекту на платформе с поддержкой Go, Rust, Java или других языков, может быть выгоднее отказаться от использования C в качестве «языка по умолчанию». Выбор инструментов, в том числе языка программирования, — это всегда компромисс. Подводные камни C, особенно неопределённое поведение в операциях со знаковыми типами, ведут к дополнительным издержкам при дальнейшем сопровождении кода, которые часто недооцениваются.
Floating point exception core dumped что за ошибка c
So I’m still pretty new to C++ and I’m trying to make a text based RPG. I just started and have been trying to work out the fighting system to be object oriented.
I get a runtime error from it that says «Floating point exception (core dumped) Process returned 136 (0x88)» as soon as you try to attack. any ideas why?
I realize this is very long and probably not programmed very well, but I’m still just starting out. Any help would be greatly appreciated!

This happens because you have the user defined twice in your code. Once in line 62 in the global scope and once in line 130 in the scope of function main. When you call newchar() in line 131 it operates on the second «user» variable, while one in global scope remains uninitialized. After that everywhere you use the «user» from the global scope, which leads to the exception here: dmg = rand() % atk + 1; because the atk variable happened to be initialized with 0 (which you should never rely on). So to solve your current problem, remove line 130, so that the global user gets initialized in line 131.
As you said, this code is far from being perfect, so I just concentrated on the question you had. To find this kind of bugs — use a debugger to see what your program does step-by-step (I assume you work in an IDE, and pretty much all of them have this functionality).
Floating point exception (core dumped)
I have a long program, which consists of one header file, and two source files, in the first one I have written the implementations of the functions, and in the second one (which is my main), I call and execute them. Though, at one point I get an error message saying
Floating point exception (core dumped)
and the program stops.
As I said there are lots of lines of code, therefore, I’m not able to post my whole source code here, though I will post the most relevant parts, and where error occurs.
My error occurs when I try to call this function (below you can find its implementation):
In this case Complex, is a type definition that I have defined.
Here is the part from the main, where this function is called.
I have earlier called some other function, which put values to fft_input and ifft_bpsk_output, which are both Complex arrays with 64 elements.