Comments 31
if(size > size+1)
В моей голове даже не возникает попытка осмыслить это. Для чего тогда вообще есть INT_MAX?!
if(size > INT_MAX-1)
Если интерпретировать выражение (size > size+1) буквально, то оно равно true для любого значения, кроме INT_MAX, т.к. INT_MAX + 1 = INT_MIN из-за конечности разрядной сетки.
Но в силу того, что по стандарту переполнение знакового целого — это UB, оптимизатор заменит выражение в скобках на константу true.
Ага, вот поэтому то же ядро FreeBSD компилилось максимум с -O2, а то и вообще -O0 :).
А почему проверка указателя на 0 — избыточна и может быть выкинута при оптимизации?
Неочевидное неопределённое поведение :)
> об указателе null, а не 0
#define NULL ((void *) 0)
>ИМХО, но тут явно недоработка компилятора.
Нет.
Нулевой указатель и нулевой адрес — это и правда разные понятия. Но константа 0 обозначает именно первый. Аналогично, проверка if(ptr)
проверяет именно соответствие ptr
нулевому указателю а не нулевому адресу.
Т.е. результат взятия значения по нему является UB. По какому праву компилятор выкинул последующую проверку на 0?
По праву того что стандарт разрешает. UB — это такая вещь после которой разрешено любое поведение программы. Как следствие, компилятор имеет право оптимизировать код из предположения что UB никогда не происходит.
UB — это такая вещь после которой разрешено любое поведение программы.Не только «после». До — тоже можно: However, if any such execution contains an undefined operation, this International Standard places no requirement on the implementation executing that program with that input (not even with regard to operations preceding the first undefined operation). (выделение моё).
Вот если программа никогда-никогда не вызвает UB — тогда и только тогда можно говорить о том, правильно её скомпилировал компилятор или нет.
Лично мне больше нравится считать С эдаким ассемблеро-заменителем. Во первых, большинство Embedded программ пишется под конкретные платформы и НЕ нуждается в переносимости. Там переносимость даже вредна. Ибо зная особенности процессора можно и нужно ими пользоваться. Во вторых, "неопределённое поведение" на самом деле вполне определённое, если мы пишем для конкретного компилятора и конкретного процессора. Я скорее за каталогизацию и документирование "определённого поведения" всех возможных компиляторов на всех возможных платформах, нежели попытку создать общий для всех и ограниченный(кастрированный), и по этому неудобный и бесполезный, сферический С в вакууме.
Конструкция (size > size + 1) идеальна! Она не зависит от разрядности числа. Компактна. Ясна. Не требует никаких констант и их определений. На мой взгляд это одна из множества жемчужин С которые надо знать и использовать.
Универсализация это всегда компромисс. Это как если у сложного и красивого многогранника сточить все грани до сферы. И самое вкусное оказывается за её пределами. К чёрту!
На мой взгляд это одна из множества жемчужин С которые надо знать и использовать.Проблема в том, что это не жемчужина C. Это жемчужина какого-то другого языка.
Ибо зная особенности процессора можно и нужно ими пользоваться.Не в том случае, когда вы пишите на C. Хотите сделать другой язык (ну там, EmC, или что-нибудь подобное) — вперёд с песней. Но не «мне больше нравится считать С» чем-то, чем он не является.
Чем является C — написано в стандарте языка. Точка. Он предназначен для написания переносимых программ — что значит что попытка вомпользоваться «особенностями процессора» с вероятностью 99% кончится тем, что вы вызовите UB и компилятор, рано или поздно, вам всю малину испортит.
Во вторых, «неопределённое поведение» на самом деле вполне определённое, если мы пишем для конкретного компилятора и конкретного процессора.Нет, конечно. Все оптимизации во всех оптимизирующих компиляторах исходят из того, что неопределённого поведения в программе нет. Обеспечить подобное — обязанность программиста.
В каких-то отдельных случаях происходит явное доопределение (ну как, например, с -fwrapv — но таких опций десятки, а неопределённых поведений в стандарте — сотни.
1) ассемблерные вставки (куда оптимизатор не лезет)
2) intrinsic-функции, которые для этого и существуют.
__m128
как float __attribute__ ((__vector_size__ (16), __may_alias__))
— то и использовании этих вещей в соответствии с описаниями не будет UB.Holix же выступает за совсем другое: без явного документирования особенностей использовать определённые UB как если бы они не были UB — исходя из того, что реально сегодня, сейчас, делает компилятор.
Это — путь в никуда: сегодня ваша программа собралась и, возможно, даже работает — но никто не может гарантировать что завтра, при выходе небольшой правки к компилятору (а иногда и к процессору — там тоже UB бывают, как это ни удивительно) все «не слетит с катушек».
«неопределённое поведение» на самом деле вполне определённое, если мы пишем для конкретного компилятора и конкретного процессораВы с implementation defined behavior не путаете часом?
2. Компилятор, внезапно, имеет абсолютно детерминированный алгоритм оптимизации, который в каждом конкретном случае приведёт к конкретному поведению.
3. На эту тему можно долго и много рассуждать, скоро напишу ещё, кстати.
1. Нет, не путаю.Таки путаете.
2. Компилятор, внезапно, имеет абсолютно детерминированный алгоритм оптимизации, который в каждом конкретном случае приведёт к конкретному поведению.Внезапно компилятор — это ещё не всё. Рассмотрите классическую программу с UB:
int *p = new int[10];
...
delete[] p;
...
p[5] = 120; // Что случится здесь?
Или ещё лучше: int *p = new int[10];
p[-2] = 17;
Поведение программы может зависеть от массы разных причин — особенно на современных системах с ASLR. И компилятор ничего с этим сделать не может.
3. На эту тему можно долго и много рассуждать, скоро напишу ещё, кстати.Давайте-давайте. Начните с пары прамеров, описанных выше, расскажите про ваш магический компилятор (у вас же он припасён в рукаве, правда?), который что-то там конкретное гарантирует, а мы будем рассматривать его в разных системах и с разными libc, tcmalloc'ами и прочим.
Только боюсь не выйдет у вас ничего. Из нескольких сотен описанных в стандарте UB компилятор может отловить (и отлавливает — см. UBSAN) дай бог несколько десятков — и то негарантированно.
1) не было упомянуто семейство *sanitizer (https://github.com/google/sanitizers), clang.llvm.org/docs/UndefinedBehaviorSanitizer.html Уже несколько лет регулярно пользуемся ими на дебажных билдах при прогоне тестов. Отловили в тестах приличное количество проблем которые или себя никак не проявляли или проявляли себя спорадически.
2) до сих пор кто-то использует супер медленный и тормозной valgrind. Что-то более или менее интенсивно потребляющее CPU или память под ним запустить практически невозможно. Asan же дает всего 2x замедление и ~2x потребление памяти. Еще одна проблема valgrind в том что множество юзкейсов связанных с таймингом на нем не воспроизводятся.
Что каждый программист на C должен знать об Undefined Behavior. Часть 2/3