Pull to refresh
31
2
Антон @tntnkn

Не шалю, пишу статьи, починяю поломанное.

Send message

Это был риторический вопрос, но спасибо)

"When you are a compiler author, you can make things up".

Ну...она не снижает надёжность результата компиляции программы, если в программе нет UB) Но тут мы начнём ходить по кругу уже)

Я как понимаю, у UB есть два глобальных прикола:

1) Неопределённое поведение не может быть определено, так как, это может привести к замедлениям на каких-то платформах. А в С++ мы не платим за то, что не используем.

2) Компилятор исходит из того, что UB в программе нет, даже когда оно там есть.

То есть, на уровне философии самого языка предполагается, что следить за UB должен не компилятор, а программист.

Если посмотреть с этой стороны, то если вы пишите под конкретный компилятор на конкретной платформе - то и UB бояться нечего, так как вы знаете, что компилятор сделает.

А так единый список UB же и делает код переносимым, разве нет?

Как понимаю, sane - это API. Пользователю, по хорошему, не нужно думать, что во что тайпдефнуто, пока имплементация сама со всем хорошо разбирается. Но void * - штука опасная, если он кастится куда-то не туда, это точно.

Мне больше всего нравится слово "sane" в примере))

"a pointer to a NULL terminated array of pointers to SANE_Device structures in *device_list.". Забориста!

Если в примере заменить условие цикла 8 на 2, то не будет UB в первую очередь.

Иф ю ноу, ю ноу

Ну, если вы делаете new[], то стандарт вам гарантирует наличие массива. Если вы делаете malloc, то такой гарантии у вас нет. В комментарии указали на целый пропоузал в стандарт, который в том числе с этим хочет разобраться.

Опять же, мы говорим конкретно сейчас про то, что написано в стандарте.

Не совсем понял вашу мысль. UB вполне себе фигурирует и в Сишном, и в плюсовом стандартах. Так что это волне себе правило языка. "Вот ситуация, вот там нам меня не расчитывай", фактически. По хорошему, UB - это ограничения на способы написания кода, которые делают этот код более портабельным.

Ну. Сложно спекулировать о том, какие приколдесы могут случиться при UB, пока такие прикалдесы не встретились на практике. Например, в этом восхитительном выступлении на CppCon есть описание совершенно зубодробительного бага, следующего как раз из UB.

Если вернуться к нашему примеру, можно попробовать представить ситуацию в которой, например, двумерный массив аллоцирован на двух страницах памяти. В конце первой страницы аллоцирован первый под-массив и кусочек второго. В начале второй страницы аллоцирован остаток второго под-массива. И вот я начинаю обходить весь двумерный массив целиком через первый под-массив. Если в каждом моменте в коде, где идёт такая итерация, используется именно обход через первый-подмассив, то может ли вторая страница быть возвращена обратно ОС? Семантически, никаких манипуляций со вторым под-массивом не происходит. А что произойдёт, если такая страница будет возвращена ОС, а потом из неё в реальности произойдёт чтение? Я такое не встречал, но вроде как ничего не мешает подобной штуке случиться.

Формально не можно. В соседнем трэде описано, почему.

2d матрицы молотите?)

Только const int (&arr)[N]. Вы передаёте prvalue.

А так да. Опускал сознательно. Про ссылки на массив вкратце упомянул в теоретической части.

К слову, никто не мешает использовать одномерный массив как многомерный при условии, что вся математика происходит внутри квадратных скобок)

А, ну... Я вот тут попытался немного порассуждать о языке "Супер-Си")

Там не "possibly hypotetical array", а "possibly hypotetical array element". Это костыль (имхо) для того, чтобы указатель мог указывать (sic) на элемент, следующий за последним в массиве, чтобы можно было писать `start < end' в цикле.

Про malloc я имел в виду, что указатель, из него выплёвываемый, формально нельзя использовать для итерации, так как под ним формально нет массива.

А так ясен-красен, что работает, как бы ещё тот же memset был сделан.

В очередной раз спасибо за подробный комментарий! Особенно нечего и добавить.

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

НО!

В случае с лямбдами всё иначе. Я дополнил ваш пример обычным вызовом лямды. В случае, когда к лямбде сначала применяется операторы разыменования, в ассемблер они всё-таки просачиваются (ну, одна операция). Код для оператора приведения к указателю на функцию (синтаксис, кстати, бодрящий!) генерируется. В отличии от случая, когда происходит просто вызов лямбы - там есть только один вызов call непосредственно сгенерированной функции.

Это бы, кстати, не отменяло возможность каста такого указателя на такой массив-объект к указателю на его первый элемент, примерно как кастуется указателя на структуру к указателю на первое поле структуры. То бишь явно.

Вообще, мне вот пришла мысль после публикации статьи, что было бы удобно в таком языке "Супер-Си" иметь отдельную штуку под массив элементов известного типа, в котором нет никаких array-to-pointer преобразований, и отдельно - указатель на какую-то "сырую" память. К массивам применять безопасную индексацию, а к указателям на "сырую" память - арифметику указателей. Пускай технически оно реализовывалось бы одинаково, но семантически разница станется огромная. Казалось бы, при чём тут `std::array`...

А я любознательный)

Сам я на Си не пишу, а подход в плюсах и так обозначил выше. Но для собственного развития пример и правда очень хороший! Может, даже будет хорошей идеей посоветовать коллегам как вопрос для интервью, чтобы проверить границу понимания приоритетов.

А я и не говорил про детали реализации. Я говорил про то, что на уровне семантики программы они не должны быть одним и тем же.

Information

Rating
1,322-nd
Location
Россия
Date of birth
Registered
Activity

Specialization

Software Developer, Application Developer
C++
English
Linux
C
Python