Comments 94
Из выше перечисленного за всё время реально сталкивался только с проблемой похожей на пример inline, но только с учётом особенностей встраивания inline для debug\release.
А вот большинство из остальных примеров даже затрудняюсь как можно написать в здравом уме.
Насколько я понял, компилятор оптимизирует оператор приведения к bool, просто подставляя true вместо bar, верно? Если бы этой оптимизации не было, а выполнение операции (bool) bar осталось для рантайма, тогда бы линковщик не смог слинковать программу (как если бы, например, мы написали std::cout << (void*)bar).
Почему компилятор может утверждать, что адрес bar не равен нулю?
Интересно, почему <<
пытается приводить адрес функции к bool
, а не пытается его вывести как есть?
Вообще-то адрес функции может быть нулевым. Надо об этом помнить, чтобы не попасть в описанную ситуацию: https://www.reddit.com/r/cpp/comments/6xeqr3/compiler_undefined_behavior_calls_nevercalled/
Читаем:
- http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
- http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_14.html
- http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_21.html
И переходите на Rust.
Вообще-то адрес функции может быть нулевым
Нет, адрес функции не может равен null pointer. По стандарту. Смотрите обсуждение ниже.
И переходите на Rust.
Да, слава Богу, что есть Rust — туда наконец-то свалят все, кто не понимает разницы между 0 и null pointer. Поскорее бы!
Возьмите адрес существующей функции, потом сравните его его с null pointer, получите в результате этого сравнения true и… тогда вы узнаете, что используете не соответствующий Стандарту компилятор :)
Возьмите адрес существующей функции, потом сравните его его с null pointer, получите в результате этого сравнения true и… тогда вы узнаете, что используете не соответствующий Стандарту компилятор :)
gcc и clang с вами не согласны.
humbug@home:~$ cat test.cpp
#include <cstdlib>
#include <iostream>
typedef int (*Function)();
static Function Do;
int main() {
std::cout << (Do == nullptr) << std::endl;
return 0;
}
humbug@home:~$ g++ test.cpp
humbug@home:~$ ./a.out
1
humbug@home:~$ clang++ -std=c++11 test.cpp
humbug@home:~$ ./a.out
1
Что-то уж совсем толсто.
Во-вторых, по поводу ваших ссылок. Если объявить переменную типа «указатель на функцию», то она (а не адрес какой-либо функции!), конечно, может иметь значение null pointer value.
Только вот в этом случае она ни на какую функцию не указывает и потому вызывать её нельзя. Более того, последнее прямым текстом сказано по одной из ваших же ссылок:
>… calling a null pointer is undefined
Представим на секунду, что у некой функции нулевой адрес (в смысле, её адрес — null pointer value). Возьмём адрес этой функции. Попробуем её вызвать через получившийся указатель… И получим неопределённое поведение. Не находите это странным? Ваша ссылка не подтверждает, а опровергает ваш тезис.
В описанной на reddit-е ситуации в программе неопределённое поведение. Неопределённое поведение может проявляться _как_угодно_, это в принципе ничего не может говорить о языке.
В данном случае компилятор видит, что указатель Do может иметь лишь два возможных значения: _либо_ null pointer value, _либо_ адрес функции EraseAll. Вызов null pointer value — неопределённое поведение, поэтому компилятор, видя вызов через указатель Do, предполагает (имеет полное право, по самому определению UB), что значением указателя является всё-таки адрес функции EraseAll — и в качестве оптимизацию производит замену вызова по указателю на вызов непосредственно EraseAll — при том, что на самом деле указатель Do всегда имеет значение null pointer value.
Это не говорит о том, что адрес функции EraseAll есть null pointer value. Это просто пример того, как компилятор честно пришёл к _ложному_ выводу по той причине, что программист нарушил контракт со своей стороны (допустил неопределённое поведение — в данном случае вызов через нулевой указатель).
Что нарушает соглашение что 0 это не валидный указатель и многие библиотеки могут не заработать в зависимости от фаз луны (по факту куда попала одна несчастная функция-первенец).
Пример такого: STM32F7xx (а это очень актуальная для IoT платформа) — там есть специальная память для кода начинающаяся с нулевого адреса специально для целей оптимизации: в архитектуре Thumb2 мало-битные константы можно загружать из кода команды, но обычные 32 битные адреса не подходят и они кладутся после тела функции, это приводит к тому что добавляются несколько доп инструкций на константу, чтоб сделать заметно быстрее код особенно для целевого применения (обработка звука и видео) специально сделана именно такая память именно для кода именно с нулевого адреса. Более того она подключена напрямую к алу процессора и только к нему, без арбитров и тд. Т.е. быстрее даже кеша.
Это даёт: я измерял на примере одного и того же кода радио модема у STM32F7 примерно 100 инструкций на семпл, у Cortex A54 (малинка) 250 инструкций на семпл, у STM32F4 — почти тоже ядро, почти тот же набор инструкции но в обычном озу с 0x20000000 300-400 инструкций на семпл. И даже быстрее Core i3. Что не удивительно т.к. на FIR фильтры и прочее ДСП требуется грузить в регистры до несколько десятков констант на выборку. Как правило в 2 раза больше констант требуется чем умножений с накоплениями.
Другой пример: находить в реалтайме с камеры треугольник и проконтролировать чтоб никто в его периметр руки не сувал — под поток плазмы (эксперементальное п/п производство). Вне этой памяти F7 даже с 640х480 не справляется (менее 5 фпс по тз), когда кладёшь в эту памяти то 7-8 фпс можно получить даже на 800х600.
В обоих случаях я заметил что компилятор специально кладёт в первое слово nop а тело функции начинается с второго слова, т.е. адрес становится 0х00000002.
Что нарушает соглашение что 0 это не валидный указатель
Это требование Стандарта, а не просто «соглашение».
A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type.(C++17 7.11 Pointer conversions [conv.ptr];
C++11/14 4.10 Pointer conversions [conv.ptr])
A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of pointer to object or pointer to function type.(C++98/03 4.10 Pointer conversions [conv.ptr])
T *p = 0;
мы получим в p некое значение, которое отличается от всех возможных значение указателей. Для платформы, где по адресу, битовое представление которого состоих из одних нулей, возможно размещение кода и/или данных, компилятор может использовать для null pointer value какое-нибудь другое битовое представление.
В любом случае преобразование адреса функции к bool гарантированно возвращает true, так как оно опирается именно на null pointer value.
Правда, учитывая количество любителей занулять структуры memset-ом
Любители конструкций с потерей типа должны страдать. По определению.
и соображения совместимости C++ с C
А в C null pointer обязательно имеет именно нулевое битовое представление?
В любом случае преобразование адреса функции к bool гарантированно возвращает true, так как оно опирается именно на null pointer value.
Да я и не возражаю.
А в C null pointer обязательно имеет именно нулевое битовое представление?
Нет, конечно. Я имею в виду, что использование memset для зануления всех полей структуры (равно как и calloc для выделения памяти под структуру с изначальным её занулением) — распространённый приём в Си, поэтому, если последовательность из нулевых байтов не будет хотя бы одним из возможных object representation для null pointer value, то сломается куча кода.
А из соображений совместимости Си и C++ у этих языков должны быть одинаковые object representation для null pointer value.
Что же касается «Про x и bar»
#include <iostream>
int x = 0;
int bar(int(x));
int main() {
std::cout << bar;
}
Не все так просто, как это написал автор. Это «хак» Weak symbol GCC и ELF, стандарт его (имхо) не оговаривает. С компилятором Visual C++ дело окончится ошибкой линкера «unresolved external symbol».
Если на некоторой платформе нулевой адрес является валидным и используемым, то на такой платформе null-указатель просто не будет представляться нулевым адресом. Null-указатели ни в С, ни в С++ никак не привязаны к нулевому адресу. Пожтому все ваши рассуждения попадают мимо кассы.
Так он как раз подтверждает ваши слова. Надо пользоваться выражениями if (ptr == NULL)
, тогда как обычно на это забивают и пишут if (ptr == 0)
.
if(ptr == 0)
абсолютно законен и переносим. Потому что литерал 0 вполне себе является null pointer constant.
Читаем http://c-faq.com/null/machexamp.html, где сказано, что в редких случаях для #define NULL
использовалось ненулевое значение.
Читаем man malloc:
The malloc() function allocates size bytes and returns a pointer to the allocated memory. The memory is
not initialized. If size is 0, then malloc() returns either NULL, or a unique pointer value that can
later be successfully passed to free().
Поэтому в контексте данных слов код стоит писать так:
ptr = malloc(...);
if (ptr == NULL)
...
Конечно же код if(ptr == 0)
законен и переносим, если вы в это верите. Молитесь на цифру 0.
Читаем c-faq.com/null/machexamp.html, где сказано, что в редких случаях для #define NULL использовалось ненулевое значение.
Это не соответствует Стандарту.
Читаем man malloc:
И? где он противоречит моим словам?
Конечно же код if(ptr == 0) законен и переносим, если вы в это верите.
В отличие от вас, я знаю. И вам советую узнать, например, отсюда
7.3.11 Pointer conversions [conv.ptr]
1 A null pointer constant is an integer literal (5.13.2) with value zero or a prvalue of type std::nullptr_t.
Занавес.
Это не соответствует Стандарту.
И вам советую узнать, например, отсюда
Я про С (что ясно из ссылок), вы про С++. Как вас мой malloc
не сагрил, это же не по фэншую не по CppCoreGuidelines.
И на будущее, когда будете писать слово стандарт с большой буквы, указывайте, что за стандарт такой) Я понимаю, что С++ окрыляет, но все же вы не одни на этом свете.
Я про С
Там всё точно так же.
не по CppCoreGuidelines
Это вещь хорошая, но не догма, и её фанатом я не являюсь.
Вот вам из стандарта Си:
6.3.2.3 Pointers
An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.66) If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.
66) The macro NULL is defined in <stddef.h> (and other headers) as a null pointer constant; see 7.19
Вот именно http://c-faq.com/null вам и нужно внимательно почитать. Там все правильно и тщательно разъяснено. Ни слова о том, что "для #define NULL
использовалось ненулевое значение" там нет.
Речь там идет о том, что на некоторых платформах null-указатель является физически ненулевым. Это правда. Однако к #define NULL
это не имеет никакого отношения. #define NULL
в С на таких платформах все равно будет 0
или (void *) 0
.
if (ptr == 0)
законно и переносимо. Это гарантируется стандартом языка С.
Это правда.
Да, спасибо, что подтвердили.
Однако к #define NULL это не имеет никакого отношения.
Да, порылся в стандарте, обновил инфу. Спасибо.
if (ptr == 0)
законно и переносимо.
Я все же предпочту сравнивать значения одинаковых типов.
Это замечательно. Я тоже принципиально предпочитаю ptr == NULL
. Однако при чем здесь "значения одинаковых типов" мне не ясно. Во-первых, NULL
запросто может быть тем же самым 0
(как в С, так и в С++). Во-вторых, даже если в С NULL
обычно предпочитают объявлять как (void *) 0
, это не делает его "значением одинакового типа" в паре с каким-нибудь не-void *
указателем.
Вы несете чушь.
Выражение ptr == 0
не является и никогда не являлось сравнением с "нулевым адресом". Согласно требования языков С и С++ выражение ptr == 0
должно быть распознано компилятором именно как сравнение с null-указателем. Компилятор обязан транслировать его в сравнение ptr
в с правильным представлением null-указателя на данной платформе. По этой причине никакой разницы между ptr == 0
и ptr == NULL
нет и никогда не было. Но всех платформах NULL
может быть определен как просто 0
, даже если на какой-то из этих платформ null-указатель представляется физически как ненулевое значение.
Почему понимание этого простой факта вызывает затруднения у начинающих С программистов — ума не приложу.
у начинающих С программистов — ума не приложу.
Да-да) Без унижений в вашей среде никак)
Вы тролль?
Нет никакого "унижения" в том, что кто-то является начинающим С программистом. Здесь никто не будет спорить с тем, что невладение концепциями "null-point constant" и "null-pointer value" и непонимание разницы между ними — это однозначное и неоспоримое свидетельство того, что индивидуум делает только первые шаги в освоении языка.
Или сделать конструктор от Foo(const Foo&&)
.
Нет. Пожалуйста, нет :)
Из-за разных настроек буферизации на момент вызова fork() заведомо неизвестно, сколько
А если перед fork() написать std::cout << std::flush, то всегда будет 1000.
Простой (но плохой) пример:
#include <utility>
#include <iostream>
template<typename T>
void do_smth(T&& arg)
{
std::cout << arg << '\n';
}
template<typename... Types>
void do_smth_with_all(Types&&... args)
{
(..., do_smth(std::forward<Types>(args)));
}
int main()
{
do_smth_with_all(9, 1.3, "string");
}
Мне пригодилась в игрушечном проекте при расчёте вклада отдельной наблюдаемой (зависящей от известного на этапе компиляции числа модельных переменных, разного для наблюдаемых разных видов) в общий градиент ошибки — выглядело это так:
(..., (gradient[asIndex(observable.vars_[ArgsIndices])] += residual * std::get<ArgsIndices>(observable_gradient)));
Здесь ArgsIndexes — параметр шаблона, объявлен какstd::size_t... ArgsIndices
"Объявления переменных"??? O_o
Запятая в объявлениях переменных не имеет никакого отношения к оператору "запятая".
1. дан массив в миллиард float с значением 1.0 кроме одного равного (1.0 плюс или минус машинный эпсилон) в произвольном месте массива.
нужно отсортировать максимально быстро при этом ОЗУ на сортировку нет вообще, даже восьми байт.
2. дана локальная статик константная переменная функции А модуле В, надо её изменить из модуля С при этом В и С друг друга не включают через инклудники, неявно и косвенно физический адрес передавать нельзя как самой переменной так и функции. Пользоваться аппратными средствами типа ДМА нельзя и даже дублированиями вирт таблиц CPU так же нельзя. Назвать минимум три принципиально разных варианта. У вас минута на обдумывание.
Вот такие вот были недавно.
Вакансия — учёный исследователь в области распознавания изображений.
Вторая задача вообще имеет решение? :)
Или там тоже теоретик нашёлся? ;)
Но в этой задаче как и во второй он хотел получить один единственный правильный ответ, пресекал попытки порассуждать в слух, расспросить больше подробностей и даже ответ «я не знаю» или перво в голову пришедшее с скоростью log2(N) типа «ну например битонная сортировка» раз ему надо «самое быстрое» — значит самодельный чип на сортировочных сетях. Вообще самое тяжолое собеседование было — что хочет не понятно, побеседовать нельзя, время оговорено а истинные параметры ответа фиксированы но не сообщаются. Так же не принимался ответ: «ну тогда не знаю», и просто промолчать оговоренный ранее таймаут. И вроде разраб а не HR, под конец ещё обиделся и проскочило устало-раздосадованное "… здец!".
телепат наверное нужен был.
Он отбрасывает свой левый аргумент и возвращает значение правого.
Это написано не точно для оператора запятая. Он ничего не отбрасывает. Просто возвращает результат своего последнего операнда. Отбрасывать свои правые, но не левые операнды могут только логические выражения.
Есть 2 подхода для реализации регулярных выражений.
Почитайте https://github.com/google/re2/wiki/WhyRE2 и тамошние ссылки. RE2 поддерживает далеко не все фичи PCRE, но большинство, и при этом работает за линейное время от длины входных данных.
for(auto i = 0; i < 1000; i++)
std::cout << "Hello world!\n";
fork();
не понял ответа… так 1000, больше или меньше?
пс: мой ответ: пока дойдет до форка, фор-луп уже закончит выполнение — поэтому 1000
А вот и нет.
std::cout
буфирезует вывод. Данные кэшируются в памяти и будут распечатаны сразу пачкой. Размер кэша зависит от библиотеки. После fork()
закэшированные данные напечатаются 2 раза.
Нужно явно сбрасывать буфер через endl
или flush
.
Например, на Mac OS у меня выводилось ровно 2000 строчек, на Linux около 1050.
Я тоже провел тест на Маке, даже задал 100,000 строчек — ровно столько же и вышло.
П.С — на с++ никогда не писал, но код (концепт) он и в африке код…
У меня в Федоре 1123 строки получается. (std::endl, в отличие от просто '\n', если что, делает flush — может быть вы его написали?)
Что будет если скомпилировать эту программу компилятором языка C?
А что будет если запустить?
GCC 7.2.0 — Segmentation fault
То, что в регулярке последний символ буква "О", а не цифра "0" наводит на мысль, что не сматчиться.
Почему только плюсовиков интересует, что будет от i++ + ++i, а во всех остальных языках народ просто пытается отрабатывать свою зарплату и не плодить говнокод? Почему книжки Саттера и Александреску вроде «200 сложных задач на С++, которые сломают твой мозг» не только печатаются, но и покупаются, а в PHP, JS, Ruby и Java тонкости языка не выходят за пределы фриковских блогов?
Единственный язык, в котором так же любят изощряться как в плюсах — это, кажется, CSS. Но им-то простительно, они люди искусства, а тут — даже не знаю, как так…
Почему только плюсовиков интересует, что будет от i++ + ++i
а в PHP, JS, Ruby и Java тонкости языка не выходят за пределы фриковских блогов?
Почему же?
В официальных экзаменах по Java достаточно подобных вопросов.
Да и в прод коде проектов на C# доводилось встречать подобное — кстати, по большей части от разрабов, которые не знают как следует язык.
К счастью, более новые языки (Swift, Kotlin) стараются избавляться от такого.
(Что касается Ruby, то мой личный опыт его изучения и опрос коллег, которые с ним работали, показывает, что там как раз имеет значение знание всех тонкостей — и этого, там, пожалуй, слишком много. Но на поверхностный взгляд может показаться, что там все просто.)
«return std::move(f);» в теле лямбды конструирует временный объект типа Foo («Foo(Foo&&)» или «Foo(const Foo&)» — не важно). Это 3-я строчка. Дальше нужно из этого временного объекта сконструировать объект f2. Значит, должна быть 4-я строчка?
en.cppreference.com/w/cpp/language/copy_elision
Надеюсь, вы никогда не увидите это в реальном коде.
«Всё, что может пойти не так, пойдет не так» (Закон Мерфи).
Возможные причины:
1) Неопытный кодер может стремиться написать наиболее «крутой» код.
2) Более опытный кодер может:
2.1 «защитить» таким образом свой код;
2.2 просто схулиганить, пошутить над коллегами, сделать себя незаменимым (никто не сможет поддерживать его код) и т.д.
Вот кажется конструктор ПЕРЕМЕЩЕНИЯ от константы — максимально бессмысленная штука. Ведь переместить из Foo константного данные нельзя
Ну, во-первых, утверждение "переместить из константного данные нельзя" формально неверно. Перемещение — широкая концепция, которая покрывает в том числе копирование. Обыкновенное копирование — это частный, наименее эффективный вариант перемещения. То есть формально "переместить" константный источник можно, просто скопировав его.
Но, во-вторых, конечно, это лишь формально-педантичная ремарка. Никакого самостоятельного смысла константные rvalue-ссылки не несут.
Про самую короткую программу
Ответ неправильный. Правило "implicit int" было полностью удалено из языка С в стандарте С99. Но даже в самом первом — "классическом" — стандарте С (ANSI/ISO C89/90) грамматически запрещались объявления с пустым decl-specifier-seq
. То есть, пользуясь правилом "implicit int" вы могли написать объявление extern var;
или const var;
, но ни в коем случае не просто var;
. Поэтому ни о каком main;
в языке С не может быть и речи.
Более того, легальность использования идентификатора main
для "посторонних" целей зависит от того, работаем ли мы в hosted evironment или в freestanding environment.
Про fork
Ответ неправильный. Поведение окружения в области буферизации зависит от массы факторов, включая тип этого окружения и его настройки.
Про move и лямбду
Ответ странный: "компилятор не умеет с ней работать"? Шито? Компилятор все прекрасно умеет: он работает совершенно верно, в полном соответствии с правилами overload resolution.
Про x и bar
Ответ странный. "Если вы хотите приведение типа, надо писать вот так int bar((int(x)));". Нет, достаточно написать так: int bar = int(x);
или, лучше, так int bar{int(x)};
Про inline
Вопрос отформатирован криво. В строке компиляции фигурируют main.cpp
и foo.cpp
. Что это за файлы? Второго варианта функции не видно, пока не открыт "Ответ". Поэтому совершенно не ясно, про что нам рассказывает автор.
Ответ правильный, но странный: распинаться про какой-то местечковый линкер нам не надо — это никому не интересно. Достаточно заметить, что в С++ требуется, чтобы inline
функция с внешним связыванием была определена одинаково во всех единицах трансляции, где она используется. Нарушение этого правила приводит к неопределенному поведению.
Про конструкторы
Наконец-то красивый вопрос!
Про самую короткую программу
Ответ неправильный. Правило «implicit int» было полностью удалено из языка С в стандарте С99. Но даже в самом первом — «классическом» — стандарте С (ANSI/ISO C89/90) грамматически запрещались объявления с пустым decl-specifier-seq. То есть, пользуясь правилом «implicit int» вы могли написать объявление extern var; или const var;, но ни в коем случае не просто var;. Поэтому ни о каком main; в языке С не может быть и речи.
Более того, легальность использования идентификатора main для «посторонних» целей зависит от того, работаем ли мы в hosted evironment или в freestanding environment
Легальность такого хака, конечно, под вопросом. Но и GCC и Clang переваривают такой код. Правда выдают warning'и -Wimplicit-int и -Wmain.
Про fork
Ответ неправильный. Поведение окружения в области буферизации зависит от массы факторов, включая тип этого окружения и его настройки.
Какие например? Кроме буфера std::cout?
Про move и лямбду
Ответ странный: «компилятор не умеет с ней работать»? Шито? Компилятор все прекрасно умеет: он работает совершенно верно, в полном соответствии с правилами overload resolution.
Да. Он умеет работать с const Foo&&. Это корректная ситуация. Но кажется, это не очевидно с первого взгляда.
Во-первых, если компилятор С выдал требуемое стандартом диагностическое сообщение, то код не считается "переваренным", даже если у вас там родился какой-то executable в результате компиляции. Так принято в мире C.
Во-вторых, не надо забывать, что GCC и Clang не являются компиляторами языка С. GCC и Clang — это в первую очередь средства для сборки разношерстной legacy codebase GNU-шных проектов, аккумулированной в течение долгих декад криворукого гамнокодинга. Она написана на некоем развеселом разношерстном "суржике" лишь внешне отдаленно напоминающем С. От GCC и Clang требуется умение в умолчательной конфигурации собирать эту codebase.
Поддержка же именно языка С является для GCC и Clang второстепенным побочным проектом, реализуемым по остаточному принципу. Язык С для этих компиляторов начинается с флагов -std=...
и -pedantic
(лучше — -pedantic-errors
). Без этого даже говорить не о чем.
Как не надо писать код