Ну...она не снижает надёжность результата компиляции программы, если в программе нет UB) Но тут мы начнём ходить по кругу уже)
Я как понимаю, у UB есть два глобальных прикола:
1) Неопределённое поведение не может быть определено, так как, это может привести к замедлениям на каких-то платформах. А в С++ мы не платим за то, что не используем.
2) Компилятор исходит из того, что UB в программе нет, даже когда оно там есть.
То есть, на уровне философии самого языка предполагается, что следить за UB должен не компилятор, а программист.
Если посмотреть с этой стороны, то если вы пишите под конкретный компилятор на конкретной платформе - то и UB бояться нечего, так как вы знаете, что компилятор сделает.
А так единый список UB же и делает код переносимым, разве нет?
Как понимаю, sane - это API. Пользователю, по хорошему, не нужно думать, что во что тайпдефнуто, пока имплементация сама со всем хорошо разбирается. Но void * - штука опасная, если он кастится куда-то не туда, это точно.
Ну, если вы делаете new[], то стандарт вам гарантирует наличие массива. Если вы делаете malloc, то такой гарантии у вас нет. В комментарии указали на целый пропоузал в стандарт, который в том числе с этим хочет разобраться.
Опять же, мы говорим конкретно сейчас про то, что написано в стандарте.
Не совсем понял вашу мысль. UB вполне себе фигурирует и в Сишном, и в плюсовом стандартах. Так что это волне себе правило языка. "Вот ситуация, вот там нам меня не расчитывай", фактически. По хорошему, UB - это ограничения на способы написания кода, которые делают этот код более портабельным.
Ну. Сложно спекулировать о том, какие приколдесы могут случиться при UB, пока такие прикалдесы не встретились на практике. Например, в этом восхитительном выступлении на CppCon есть описание совершенно зубодробительного бага, следующего как раз из UB.
Если вернуться к нашему примеру, можно попробовать представить ситуацию в которой, например, двумерный массив аллоцирован на двух страницах памяти. В конце первой страницы аллоцирован первый под-массив и кусочек второго. В начале второй страницы аллоцирован остаток второго под-массива. И вот я начинаю обходить весь двумерный массив целиком через первый под-массив. Если в каждом моменте в коде, где идёт такая итерация, используется именно обход через первый-подмассив, то может ли вторая страница быть возвращена обратно ОС? Семантически, никаких манипуляций со вторым под-массивом не происходит. А что произойдёт, если такая страница будет возвращена ОС, а потом из неё в реальности произойдёт чтение? Я такое не встречал, но вроде как ничего не мешает подобной штуке случиться.
Там не "possibly hypotetical array", а "possibly hypotetical array element". Это костыль (имхо) для того, чтобы указатель мог указывать (sic) на элемент, следующий за последним в массиве, чтобы можно было писать `start < end' в цикле.
Про malloc я имел в виду, что указатель, из него выплёвываемый, формально нельзя использовать для итерации, так как под ним формально нет массива.
А так ясен-красен, что работает, как бы ещё тот же memset был сделан.
В очередной раз спасибо за подробный комментарий! Особенно нечего и добавить.
Кстати, в приколе с приведением функции к указателю в вашем примере забавно то, что и в IR, и в ассемблере ничего этого нет даже без оптимизаций. В какой-то мере, операция это максимально бессмысленная, но всё-равно существующая.
НО!
В случае с лямбдами всё иначе. Я дополнил ваш пример обычным вызовом лямды. В случае, когда к лямбде сначала применяется операторы разыменования, в ассемблер они всё-таки просачиваются (ну, одна операция). Код для оператора приведения к указателю на функцию (синтаксис, кстати, бодрящий!) генерируется. В отличии от случая, когда происходит просто вызов лямбы - там есть только один вызов call непосредственно сгенерированной функции.
Это бы, кстати, не отменяло возможность каста такого указателя на такой массив-объект к указателю на его первый элемент, примерно как кастуется указателя на структуру к указателю на первое поле структуры. То бишь явно.
Вообще, мне вот пришла мысль после публикации статьи, что было бы удобно в таком языке "Супер-Си" иметь отдельную штуку под массив элементов известного типа, в котором нет никаких array-to-pointer преобразований, и отдельно - указатель на какую-то "сырую" память. К массивам применять безопасную индексацию, а к указателям на "сырую" память - арифметику указателей. Пускай технически оно реализовывалось бы одинаково, но семантически разница станется огромная. Казалось бы, при чём тут `std::array`...
Сам я на Си не пишу, а подход в плюсах и так обозначил выше. Но для собственного развития пример и правда очень хороший! Может, даже будет хорошей идеей посоветовать коллегам как вопрос для интервью, чтобы проверить границу понимания приоритетов.
Это был риторический вопрос, но спасибо)
"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`...
А я любознательный)
Сам я на Си не пишу, а подход в плюсах и так обозначил выше. Но для собственного развития пример и правда очень хороший! Может, даже будет хорошей идеей посоветовать коллегам как вопрос для интервью, чтобы проверить границу понимания приоритетов.
А я и не говорил про детали реализации. Я говорил про то, что на уровне семантики программы они не должны быть одним и тем же.