Pull to refresh

Comments 94

Спасибо за головоломки в утро субботы)
Из выше перечисленного за всё время реально сталкивался только с проблемой похожей на пример inline, но только с учётом особенностей встраивания inline для debug\release.
А вот большинство из остальных примеров даже затрудняюсь как можно написать в здравом уме.
Последний пример в той или иной мере часто встречается на практике когда люди случайно пишут:
std::unique_lock<std::mutex> (m_mtx);

В намерениях было залочить мьютекс m_mtx, а в итоге объявляют локальную переменную типа unique_lock с именем m_mtx.
У меня вопрос про «x и bar».
Насколько я понял, компилятор оптимизирует оператор приведения к bool, просто подставляя true вместо bar, верно? Если бы этой оптимизации не было, а выполнение операции (bool) bar осталось для рантайма, тогда бы линковщик не смог слинковать программу (как если бы, например, мы написали std::cout << (void*)bar).

Почему компилятор может утверждать, что адрес bar не равен нулю?

Интересно, почему << пытается приводить адрес функции к bool, а не пытается его вывести как есть?

Потому что operator<< перегружен для const void* (к которому приводятся любые объектные типы), но не для указателей на функции (кроме функций с сигнатурами манипуляторов вроде endl или flush). Выводить адреса объектов полезнее, чем адреса функций.

Вообще-то адрес функции может быть нулевым. Надо об этом помнить, чтобы не попасть в описанную ситуацию: https://www.reddit.com/r/cpp/comments/6xeqr3/compiler_undefined_behavior_calls_nevercalled/


Читаем:



И переходите на Rust.

Вообще-то адрес функции может быть нулевым

Нет, адрес функции не может равен null pointer. По стандарту. Смотрите обсуждение ниже.
И переходите на Rust.

Да, слава Богу, что есть Rust — туда наконец-то свалят все, кто не понимает разницы между 0 и null pointer. Поскорее бы!

Молодой человек, в данном треде обсуждают возможность адреса функции принимать значение нуля, то есть 0. Что и было доказано прувом. Хотите больше? Получите.

Вы, старый человек, не понимаете разницы между «адресом функции» и «указателем на функцию». Вам действительно лучше писать на Rust… Или нет — на Python.

Возьмите адрес существующей функции, потом сравните его его с 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
Я смотрю вы всё так же не хотите в упор видеть разницу между адресом функции и указателем на функцию :D Неужели вам кто-то за программирование зарплату платит?
Вас просили взять «адрес существующей функции». Функция у вас здесь одна — это main, и адрес вы её не берёте (да и нельзя по Стандарту). Вы вообще никакой адрес здесь не берёте, а просто сравниваете value-initialized переменную типа «указатель на функцию» с null pointer value.

Что-то уж совсем толсто.
Пардон — не value-initialized, а zero-initialized. Но для переменной типа «указатель на функцию» это одно и то же.
Не вижу по вашим ссылкам функций с адресом равным пустому указателю.
Во-первых, я здесь приводил прямую цитату из Стандарта C++. Не может.

Во-вторых, по поводу ваших ссылок. Если объявить переменную типа «указатель на функцию», то она (а не адрес какой-либо функции!), конечно, может иметь значение 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])
Как вы можете заметить, Стандарт нигде не говорит, что битовое представление null pointer value будет состоять из всех нулей. Он лишь говорит, что написав
T *p = 0;

мы получим в p некое значение, которое отличается от всех возможных значение указателей. Для платформы, где по адресу, битовое представление которого состоих из одних нулей, возможно размещение кода и/или данных, компилятор может использовать для null pointer value какое-нибудь другое битовое представление.
Верно. Правда, учитывая количество любителей занулять структуры memset-ом (и соображения совместимости C++ с C) и просто неудобство подхода, на практике разработчики компиляторов на такое никогда не пойдут)

В любом случае преобразование адреса функции к 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.
UFO just landed and posted this here
Здесь таится ловушка. Дело в том, что конверсия «null pointer constant» в указатель в стандарте оговорена отдельно, при этом для финального значения конверсии (т.е. значения указателя) не гарантируется битовое значение «все нули». Вместо этого указывается что это implementation-defined.

Что же касается «Про 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" и непонимание разницы между ними — это однозначное и неоспоримое свидетельство того, что индивидуум делает только первые шаги в освоении языка.

Потому что это адрес функции, он «по определению» не может быть нулевым (можно покопаться в стандарте — но как минимум это так в подавляющем большинстве реализаций).

В такой постановке вопроса лошадь ставится позади телеги.
В стандартах языков С и С++ этот момент оговаривается с противоположной стороны: объектное представление нулевого указателя не должно соответствовать указателю ни на одну валидную сущность соответствующего типа (объект или функцию).

Я правильно понимаю, что по fork правильный ответ «1000 или больше»?

Из-за разных настроек буферизации на момент вызова fork() заведомо неизвестно, сколько строксимволов уже отправлено на вывод, а сколько ещё ждёт очереди.
А если перед fork() написать std::cout << std::flush, то всегда будет 1000.
Да. Больше 1000. Можно ещё сбросить буфер через std::endl;
Когда на практике используется оператор запятая, кроме циклов и объявления переменных?
Из всех вариантов единственный, за который не надо бить ногами, это использование в 3-й части for. И да, запятая в объявлении переменных, это не оператор «запятая», а несколько иная сущность.
Спасибо. А чем её использование в ассертах или debug-макросах плохо?
В C++17 fold-expression с операцией следования позволяет без рекурсии совершить произвольное действие над всеми элементами parameter pack-а.
Простой (но плохой) пример:
#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
Почитайте исходники Qt. Там до появления в стандарте (и в реализациях) шаблонов с переменных числом аргументов очень много магии было сделано с хитростями, в том числе с оператором запятая для удобства. Если мне не изменяет память, то где-то в передаче аргументов «функции».

"Объявления переменных"??? O_o
Запятая в объявлениях переменных не имеет никакого отношения к оператору "запятая".

Надеюсь, я никогда не увижу эти головоломки на собеседовании.
всё гораздо хуже, сейчас модны крайне идиотские задачки вида:
1. дан массив в миллиард float с значением 1.0 кроме одного равного (1.0 плюс или минус машинный эпсилон) в произвольном месте массива.
нужно отсортировать максимально быстро при этом ОЗУ на сортировку нет вообще, даже восьми байт.
2. дана локальная статик константная переменная функции А модуле В, надо её изменить из модуля С при этом В и С друг друга не включают через инклудники, неявно и косвенно физический адрес передавать нельзя как самой переменной так и функции. Пользоваться аппратными средствами типа ДМА нельзя и даже дублированиями вирт таблиц CPU так же нельзя. Назвать минимум три принципиально разных варианта. У вас минута на обдумывание.

Вот такие вот были недавно.
Вакансия — учёный исследователь в области распознавания изображений.
извиняюсь забыл уточнить про пункт 1: человек очень хотел поговорить про О больше алгоритмов сортировки и категорически не принимал ответ что построить гистограмму, просто пройтись до первого неповторяющегося и положить его в начало-конец и даже то что в реальных задача всегда есть шум и такая мелкая погрешность не важна — игнорировались, ему надо было потеоретезировать про сами алгоритмы.
Да уж.
Вторая задача вообще имеет решение? :)

Или там тоже теоретик нашёлся? ;)
нередко задачки идут либо без решения — когда собеседующий что либо хотел сделать но ответа правильного не знает, либо заведомо неправильные чтоб проверить ход мыслей/знание стандартов.
Но в этой задаче как и во второй он хотел получить один единственный правильный ответ, пресекал попытки порассуждать в слух, расспросить больше подробностей и даже ответ «я не знаю» или перво в голову пришедшее с скоростью log2(N) типа «ну например битонная сортировка» раз ему надо «самое быстрое» — значит самодельный чип на сортировочных сетях. Вообще самое тяжолое собеседование было — что хочет не понятно, побеседовать нельзя, время оговорено а истинные параметры ответа фиксированы но не сообщаются. Так же не принимался ответ: «ну тогда не знаю», и просто промолчать оговоренный ранее таймаут. И вроде разраб а не HR, под конец ещё обиделся и проскочило устало-раздосадованное "… здец!".
телепат наверное нужен был.
Он отбрасывает свой левый аргумент и возвращает значение правого.

Это написано не точно для оператора запятая. Он ничего не отбрасывает. Просто возвращает результат своего последнего операнда. Отбрасывать свои правые, но не левые операнды могут только логические выражения.

Да. Левый операнд запятой всегда будет вычислен.
А я уже начал забывать почему отказался от C++, спасибо за напоминание.
Зато с С++ никогда не скучно.
Я так и не понял, как не надо писать код =(

Из статьи же очевидно, что не надо писать код на C/C++ :)
На C/C++ точно не надо, ужасный язык. Надо на C++, или, на худой конец, на C.
Есть 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.

А разве \n не эквивалентен endl?
Вот тут написано так:
Некоторые реализации буферизуют вывод в stdout построчно, и запись \n сама будет вызывать flush.

Наверное, именно поэтому не у всех возникает описанная проблема.
я понимаю что вывод на печать — он асинхронный, также понимаю про shared/duplicated resources. Но команда вывода на печать вызывается то только из первого процесса. А во втором — ну ок, есть продублированный кеш, но никто не запрашивал вывод печати.

Я тоже провел тест на Маке, даже задал 100,000 строчек — ровно столько же и вышло.
П.С — на с++ никогда не писал, но код (концепт) он и в африке код…

flush всегда автоматически делается после окончания работы программы. И делается он почему-то в обоих процессах, а не только в одном :-) А почему у вас на Маке вышло ровно 100,000 строк — da-nie объяснил выше.

без \n на тестах выходит тоже самое, и вообще почему почему endl должен влиять на флоу программы?
и еще что-то — я сначала подумал что вывод на печать он асинхронный, но похоже я ошибся и он синхронный, и при таком раскладе вообще не понятно, как может напечататься больше 1000…
Не асинхронный, а буферизованный. flush дёргается при завершении программы из деструктора глобального объекта класса std::ios_base::Init: en.cppreference.com/w/cpp/io/ios_base/Init

У меня в Федоре 1123 строки получается. (std::endl, в отличие от просто '\n', если что, делает flush — может быть вы его написали?)
А вот под QNX 6.3 SP 3 1055 строчек выводит. :)
Что будет если скомпилировать эту программу компилятором языка 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, то мой личный опыт его изучения и опрос коллег, которые с ним работали, показывает, что там как раз имеет значение знание всех тонкостей — и этого, там, пожалуй, слишком много. Но на поверхностный взгляд может показаться, что там все просто.)

Почему в примере «про move и лямбду» выводятся только 3 строчки в cout?

«return std::move(f);» в теле лямбды конструирует временный объект типа Foo («Foo(Foo&&)» или «Foo(const Foo&)» — не важно). Это 3-я строчка. Дальше нужно из этого временного объекта сконструировать объект f2. Значит, должна быть 4-я строчка?
Стандарт явно разрешает (а в C++17 — даже требует) компилятору устранять временные объекты (а также, в меньшей степени, именованные) путём конструирования объектов непосредственно в конечной точке цепи копирований/перемещений.
en.cppreference.com/w/cpp/language/copy_elision
Надеюсь, вы никогда не увидите это в реальном коде.


«Всё, что может пойти не так, пойдет не так» (Закон Мерфи).

Возможные причины:

1) Неопытный кодер может стремиться написать наиболее «крутой» код.
2) Более опытный кодер может:
2.1 «защитить» таким образом свой код;
2.2 просто схулиганить, пошутить над коллегами, сделать себя незаменимым (никто не сможет поддерживать его код) и т.д.

> Или сделать конструктор от Foo(const Foo&&).
Вот кажется конструктор ПЕРЕМЕЩЕНИЯ от константы — максимально бессмысленная штука. Ведь переместить из 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). Без этого даже говорить не о чем.

Первый пример не компилируется: Error noname00.c 1: Type name expected
Я проверял с последним clang 7 и GCC 8 на godbolt.org.
Компилируется с warning'ами копилятором C (не С++).

Нет. Если упомянутый вами "warning" является требуемым стандартом языка диагностическим сообщением, то в мире языка С это называется "НЕ компилируется".

Sign up to leave a comment.

Articles