Во-вторых, присваивание - это операция, которая выполняется на уже инициализированной (уже сконструированой) переменной. Здесь же никакой сконструированной переменной нет (мы только пытаемся ее сконструировать), поэтому и никакого присваивания здесь никак быть не может в принципе.
Пробежал глазами статью (честно признаюсь, что пока не разбирал детально), но так и не нашел одного из самых главных и самых важных для понимания фактов о структуре объявления.
Объявление в С++ имеет не тот базовый вид, который приведен в статье, а вид
type cv-qual pr-specs name fa-specs, pr-specs name fa-specs, ..., pr-specs name fa-specs;
(идя на поводу у предложенного статьей формата).
То есть после общей частиtype cv-qual следуют множественные деклараторы pr-specs name fa-specs, разделенные запятыми. При этом ключевым моментом для правильного понимания семантики такого объявления является то, что часть type cv-qual является общей для всех и относится ко всем деклараторам. В отличие от части pr-specs, которая является индивидуальной для каждого декларатора.
Именно этот факт приводит к тому, что cv-квалификаторы в составе cv-qual ведут себя не так, как cv-квалификаторы в составе индивидуальных pr-specs. Первые относятся ко всем деклараторам в объявлении, а вторые - только к своему декларатору. Именно поэтому никаких аргументов в пользу синтаксиса со смещенным вправо const не существует и какая-то пропаганда такого синтаксиса никем всерьез не рассматривается. Cv-квалификаторы в составе cv-qual всегда смещаются влево именно для подчеркивания вышеописанной особенности структуры объявления.
Если это примут, то это будет очередной шаг к смерти С++. Причем если насаждение знаковых типов в языке вызывало лишь удивленный взлет бровей, то вот эта бредятина - это уже будет один из гвоздей в крышку гроба.
"Early return" не имеет прямого отношения к проверке граничных условий. "Early return" в компании с братом "early continue" - паттерн категории divine (т.е. применение его обязательно и эта обязательность не подлежит обсуждению), направленный на выделение и ранее отсечение простых ситуаций в рамках более сложной логики:
В рамках реализации относительно сложной логики всегда старайтесь сразу выделить, отсечь и обработать более простые варианты, в порядке повышения сложности. Т.е. в первую очередь отсекается и выполняется самая тривиальная обработка. Завершенность обработки должна быть обязательно подчеркнута явным return (в функции) или continue (в итерации цикла)
Тщательное следование этому правилу существенно повышает удобочитаемость кода.
Вы же почему-то "удавили" этот паттерн до проверки граничных условий на аргументах... Это - дискредитация идеи.
А то, что это все в вашем коде выглядит громоздко, является следствием [явно нарочитого] применения фейкового "очень полезного правила" (c), т.е. фактически антипаттерна, "всегда заключай ветки if в скобки, даже если это не нужно"
Все люди в мире свято верят в незыблемый принцип: "Никогда не обобщайте и не говорите за других". Не надо проецировать свои фрустрации, вызванные фрагментальностью ваших знаний, на всех.
До тех пор, пока мы остаемся в рамках языка С, формально "ситуация отдана на откуп авторам конкретного компилятора" - это "implementation-defined behavior". А попытка модификации строкового литерала - это undefined behavior. Это сильно разные вещи.
Конкретная реализация имеет право реализовывать расширения языка, в том числе конкретная реализация имеет право определять поведение в случае undefined behavior. Но это уже выходит за рамки языка С и никакого отношения к С не имеет.
В С и С++ самый левый const возле самого типа являются частью спецификатор-типа, а не частью индивидуального декларатор (сравните с *, которая наоборот, является частью индивидуального декларатор, а не спецификатор-типа).
Именно по этой причине грамотные программисты предпочитают выравнивать звездочки вправо, а cv-спецификаторы - влево. Более того, чтобы подчеркнуть особую грамматическую роль этого самого левого constего предпочитают писать слева от имени типа.
Правильно:
const int *const *a;
Неправильно:
int const *const *a;
Попытки притащить сюда за уши соображения некоего "единообразия", которое якобы просматривается во втором варианте - это не более чем попытки натянуть сову на глобус. В языках С и С++ принципиально нет и никогда не было этого единообразия. Помните, что const возле спецификатора типа - это совершенно иная, астрономически отличающаяся по своей сути от остальных const сущность. И попытки создавать косметическую иллюзию наличия "единообразия" между такими const - это не более чем косметический обман.
В конечном итоге, если вам на уровне персональных предпочтений нравится выравнивать const вправо - ваше право. Но не пытайтесь подвести под это какое-то теоретическое обоснование. Все теоретические обоснования сразу же ведут к тому, что правильнее писать именно const int *const *a;
Дело в том, что в C тип символьного литерала имеет тип int, а не char. Поэтому такие функции стандартной библиотеки языка C как char *strchr( const char *str, int ch ) принимают int, а не char в аргументе ch.
Это - совершенно не верное объяснение. Тип символьной константы здесь совершенно ни при чем.
Причина, по которой стандартные функции принимают тип int заключается в том, что в старинном K&R С у функций не было прототипов. Функции либо объявлялись как (), либо вызывались без объявления вообще. Все это разрешалось и в первом стандарте С - С89/90. При вызовах таких функций передаваемые аргументы всегда безусловно подвергались default argument promotions, о которых вы сами уже упоминали. В процессе default argument promotions тип char превращается в тип int и в функцию передается уже именно int. Вот именно поэтому у "классических" стандартных функций вы никогда не увидите параметров типа char, а вместо них будуь параметры типа int.
По этой же самой причине вы никогда не увидите у "классических" стандартных функций параметров типа short или float. Другими словами, в K&R C вообще не было возможности передать параметры типа char, short или float. Эти типы передавались как int, int и double соответственно.
А ваши домыслы про влияние типа символьного литерала тут совершенно ни при чем.
С ключевым словами - неверно. В С есть _Bool, _Complex, _Imaginary и т.д.
Что касается longjmpто различие между С и С++ тут не настолько сильны, как может показаться. Во-первых, в С менеджмент ресурсов делается рукописным эпилог-кодом. И он точно так же "не выполнится" при выходе из функции по longjmp. Во-вторых, в С уже тоже появились [опциональные] неявные конструкторы и деструкторы - это внутренний код, обслуживающий VLA. Именно поэтому описанию longjmp в совокупности с VLA уделено отдельное внимание в стандарте языка.
Неверно. Строковые литералы и в С и в С++ являются массивами и имеют тип char [N] в языке С и const char [N] в языке С++. Независимо от типа, строковые литералы являются немодифицируемыми объектами и в С, и в С++.
Уже с самого начала - дичайшее количесто ошибок в статье. Просто ошибка на ошибке.
Потому что объявление void f() является эллипсисом в языке C (ellipsis notation; их также часто называют как variadic arguments
Что??? Эллипсис в языке С - это именно ..., то есть действительно variadic arguments. Но объявление c ()никакого отношения к ellipsis или variadic arguments не имеет и никогда не имело. Это т.наз. объявление без прототипа, которое не указывает список параметров. Я не буду расписывать подробно, но замечу, что такое объявление не совместимо с ellipsis-определением. Функции с ellipsis-определением в С, то есть f(...), обязаны быть объявлены заранее именно с ..., иначе при вызове такой функции поведение не определено. Это одно из мест "классического" стандартного С, в котором требуется ранее (до первого вызова) объявление функции.
Т. е. следующий код абсолютно корректен и компилируется любым С-компилятором:
voidf() { printf("f()"); }
intmain() { f(5, 3.2f, "test");
Да, такой код является формально корректным из-за того, что функция объявлена без прототипа. Однако поведение такого кода не определено. Вызов функции без прототипа эквивалентен вызову вообще не объявленной функции (в С89/90 такое разрешалось). При вызове функции без прототипа компилятор С обязан "придумать" прототип функции на основе переданных аргументов. В данном случае это будет f(int, double, char *)(именно double). Если при этом оказывается, что "придуманный" прототип не совпадает с фактическим определением функции, то проведение не определено. Именно это происходит и в вашем случае - проведение не определено.
Это "абсолютно корректен" или нет?
Чтобы указать компилятору языка C, что функция не принимает аргументов, нужно указать это явно с помощью аргумента void:
Ключевым различием здесь, которую я уже упомянул выше, является "объявление с прототипом" против "объявления без прототипа". Объявление с (void) - это уже объявление с прототипом. Кстати, объявления без прототипа давно являются deprecated и в С23 будут, наконец, формально запрещены.
Итак, подытожим в виде таблицы:
Таблица повторяет вышеуказанную бредятину о том, что () - это якобы "эллипсис". Это грубейше неверно.
Кстати, обратите внимание, в языках C/C++ аргументы часто передаются справа налево по C-декларации, это как раз нужно для того, чтобы работали такие функции как printf(). Чтобы на вершине стека был параметр, по которому мы сможем определить сколько данных лежит еще на стеке.
Очередной заряд полнейшей чуши... Функции printf()ничего подобного не нужно. Никакой передачи параметров "справа налево" не существует. Это не говоря уже о том, что современные ABI позволяют передавать даже variadic аргументы в регистрах процессора.
Историческая справка: до появления стандарта ANSI C, был и альтернативный стиль объявления аргументов функций, т. н. стиль Кернигана и Ричи (стиль K&R). Пример кода (нужно компилировать C компилятором):
Во-первых, то, о чем вы писали выше, это и есть "альтернативный стиль K&R". Всякий раз , когда в языке С вы пишете ()в объявлении или определении функции - это объявление без прототипа, то есть тот самый "альтернативный стиль K&R" - один из его частных случаев. Чтобы уйти от "альтернативного стиля K&R" для функции без параметров мы и пишем (void)
Во-вторых, не "до появления стандарта ANSI C", а и в стандарте ANSI С тоже. Стандарт ANSI С разрешает K&R объявления, хотя там они давно являются deprecated. Стандарт С89/90 разрешал вызов функций вообще без объявления. С99 запретил вызов без объявления, оставив однако возможность делать объявления без прототипа. Только C23 запрещает объявления без прототипа и K&R-стиль в целом. При этом хотя объявления функций c ()являются K&R объявлениями, в С23 они запрещены не будут - их "сконвертировали" в эквивалент (void), то есть все станет в точности как как в С++.
При передаче аргументов в variadic function применяются следующие правила неявных приведений типов аргументов (правило default argument promotions):
Во-первых, default argument promotions состоят из integer promotions плюс преобразование float в double. Вы развернули описание, но у вас однако integer promotions описаны не совсем корректно. Ну да ладно.
Во-вторых, default argument promotions делаются при передаче не только variadic аргументов, но и при вызове функций без прототипа (именно поэтому в примере выше я указал, что передача аргумента 3.2fприводит к "придуманному" прототипу с параметром double). Вы это все, конечно, попытались запихать в свою теорию "это всё эллипсис", но это - полная чушь.
Дело в то, что существуют различные т. н. calling convensions (соглашения о вызовах функций)
Никакого отношения к языкам С и С++ это, разумеется, не имеет. Никаких calling conventions ни в С, ни в С++ нет. Что это делает в статье про "особенности С и С++" - не ясно.
А что на счет строковых литералов, например "string literal"? Это будет char*
Грубейше не верно. Строковый литерал - это массив, то есть char[N] в языке С и const char[N] в C++.
Ответ очень прост: char, signed char и unsigned char — это три разных типа. Но при этом стандарт не запрещает, чтобы char был псевдонимом либо signed char, либо unsigned char.
"Псевдонимами" в терминологии С и С++ называют идентичные типы, то есть одни и те же типы под разными именами. Так как char, signed char и unsigned charкак вы правильно заметили - это три разных типа, никаких "псевдонимов" тут нет и быть не может. О каком "стандарт не запрещает" вы ведете речь - не ясно.
Это не ошибка, это бессмыслица. Если уж автор воспользовался memcpy, то union ему действительно не нужен. Собственно, потому он и не заметил этой "ошибки", что код прекрасно работает и так. Union с фиктивными полями иногда приходится использовать для, целей выравнивания, но здесь совсем не тот случай. Автор наворотил ненужной фигни с union поверх вполне работоспособной техники и, похоже, до сих пор не понял, что это лишь ненужная фигня.
Использованный в коде union содержит поле as_str. Однако нигде в коде это поле не используется, что говорит о том, что ни это поле никому тут нинафиг не нужно, ни весь union никому тут нинафиг не нужен. Зачем код замусорен всем этим? В чем глубинный смысл?
Статья содержит кашу из как минимум двух разных типов "малоизвестности": действительно интересных темных уголков языка и банальностей из разряда "я просто ленив и не знаю о существовании С99". Непонятно, зачем было мешать это в одну кучу. Compound literals являются "малоизвестной" фичей языка? Серьезно?
Классика вроде Duff's device - неизвестная возможность? Серьезно? Это редко используемая возможность, но ничего "неизвестного" в ней нет.
"Несуразные объявления указателей" - вообще постоянно и массово используемые объявления. Как это сюда попало?
Про "Совместимые объявления и массивы как параметры функций" написана некорректная ерунда. Язык С накладывает разные требования на объявления одной и той же функции, сделанные в разных единицах трансляции и сделанные в одной и той же единице трансляции. От объявлений в одной единице трансляции требуется полное совпадение типа функции. От объявлений в разных единицах трансляции требуется именно совместимость типов. Подчеркну: совместимость - это именно для объявлений, сделанных в разных единицах трансляции. А в статье приведены последовательные объявления в одной единице трансляции и ведется разговор о совместимости...
В сильно заезженной теме "a[b] буквально эквивалентно (a + b) " допущена явная опечатка. Должно быть *(a + b).
"Одиночный #" - это на самом деле интересная тема, которую автор не раскрыл. В стародавние времена, когда проход препроцессора по коду был дорогим удовольствием, существовало соглашение, что исходный файл обрабатывается препроцессором только в том случае, если его первый символ - #. Как правило директивы #include стоят в самом начале файла, то есть это соглашение было вполне разумным. Но в некоторых случаях получалось так, что файл не начинался с #, а обработку препроцессором все-таки нужно было запросить. Вот именно в этом случае в файл первой строкой ставили пустую директиву #. Сегодня эта возможность уже не актуальна, а пустая директива может использоваться разве что для косметического оформляжа.
Эта два варианта не совсем эквивалентны (хотя в последних стандартах языка они почти эквивалентными). Есть, однако, нюансы.
Однако в остальном - именно так. Это именно вызов конструктора в обоих случаях.
Во-первых, потому что в стандарте так сказано.
Во-вторых, присваивание - это операция, которая выполняется на уже инициализированной (уже сконструированой) переменной. Здесь же никакой сконструированной переменной нет (мы только пытаемся ее сконструировать), поэтому и никакого присваивания здесь никак быть не может в принципе.
Пробежал глазами статью (честно признаюсь, что пока не разбирал детально), но так и не нашел одного из самых главных и самых важных для понимания фактов о структуре объявления.
Объявление в С++ имеет не тот базовый вид, который приведен в статье, а вид
type cv-qual pr-specs name fa-specs, pr-specs name fa-specs, ..., pr-specs name fa-specs;
(идя на поводу у предложенного статьей формата).
То есть после общей части
type cv-qual
следуют множественные деклараторыpr-specs name fa-specs
, разделенные запятыми. При этом ключевым моментом для правильного понимания семантики такого объявления является то, что частьtype cv-qual
является общей для всех и относится ко всем деклараторам. В отличие от частиpr-specs
, которая является индивидуальной для каждого декларатора.Именно этот факт приводит к тому, что cv-квалификаторы в составе
cv-qual
ведут себя не так, как cv-квалификаторы в составе индивидуальныхpr-specs
. Первые относятся ко всем деклараторам в объявлении, а вторые - только к своему декларатору. Именно поэтому никаких аргументов в пользу синтаксиса со смещенным вправоconst
не существует и какая-то пропаганда такого синтаксиса никем всерьез не рассматривается. Cv-квалификаторы в составеcv-qual
всегда смещаются влево именно для подчеркивания вышеописанной особенности структуры объявления.Если это примут, то это будет очередной шаг к смерти С++. Причем если насаждение знаковых типов в языке вызывало лишь удивленный взлет бровей, то вот эта бредятина - это уже будет один из гвоздей в крышку гроба.
... с той только ремаркой, что это возможно только в С++, но не в С.
"Early return" не имеет прямого отношения к проверке граничных условий. "Early return" в компании с братом "early continue" - паттерн категории divine (т.е. применение его обязательно и эта обязательность не подлежит обсуждению), направленный на выделение и ранее отсечение простых ситуаций в рамках более сложной логики:
В рамках реализации относительно сложной логики всегда старайтесь сразу выделить, отсечь и обработать более простые варианты, в порядке повышения сложности. Т.е. в первую очередь отсекается и выполняется самая тривиальная обработка. Завершенность обработки должна быть обязательно подчеркнута явным
return
(в функции) илиcontinue
(в итерации цикла)Тщательное следование этому правилу существенно повышает удобочитаемость кода.
Вы же почему-то "удавили" этот паттерн до проверки граничных условий на аргументах... Это - дискредитация идеи.
А то, что это все в вашем коде выглядит громоздко, является следствием [явно нарочитого] применения фейкового "очень полезного правила" (c), т.е. фактически антипаттерна, "всегда заключай ветки if в скобки, даже если это не нужно"
Все люди в мире свято верят в незыблемый принцип: "Никогда не обобщайте и не говорите за других". Не надо проецировать свои фрустрации, вызванные фрагментальностью ваших знаний, на всех.
До тех пор, пока мы остаемся в рамках языка С, формально "ситуация отдана на откуп авторам конкретного компилятора" - это "implementation-defined behavior". А попытка модификации строкового литерала - это undefined behavior. Это сильно разные вещи.
Конкретная реализация имеет право реализовывать расширения языка, в том числе конкретная реализация имеет право определять поведение в случае undefined behavior. Но это уже выходит за рамки языка С и никакого отношения к С не имеет.
Мой комментарий выше содержит ответ на этот вопрос.
Наследие или нет, это особенность грамматики С и С++. Грамматика (объявления) в С и С++ упрощенно такова
<спецификатор-типа> <декларатор>, <декларатор>, ..., <декларатор>;
В С и С++ самый левый
const
возле самого типа являются частьюспецификатор-типа
, а не частью индивидуальногодекларатор
(сравните с*
, которая наоборот, является частью индивидуальногодекларатор
, а неспецификатор-типа
).Именно по этой причине грамотные программисты предпочитают выравнивать звездочки вправо, а cv-спецификаторы - влево. Более того, чтобы подчеркнуть особую грамматическую роль этого самого левого
const
его предпочитают писать слева от имени типа.Правильно:
const int *const *a;
Неправильно:
int const *const *a;
Попытки притащить сюда за уши соображения некоего "единообразия", которое якобы просматривается во втором варианте - это не более чем попытки натянуть сову на глобус. В языках С и С++ принципиально нет и никогда не было этого единообразия. Помните, что
const
возле спецификатора типа - это совершенно иная, астрономически отличающаяся по своей сути от остальныхconst
сущность. И попытки создавать косметическую иллюзию наличия "единообразия" между такимиconst
- это не более чем косметический обман.В конечном итоге, если вам на уровне персональных предпочтений нравится выравнивать
const
вправо - ваше право. Но не пытайтесь подвести под это какое-то теоретическое обоснование. Все теоретические обоснования сразу же ведут к тому, что правильнее писать именноconst int *const *a;
Далее:
Это - совершенно не верное объяснение. Тип символьной константы здесь совершенно ни при чем.
Причина, по которой стандартные функции принимают тип
int
заключается в том, что в старинном K&R С у функций не было прототипов. Функции либо объявлялись как()
, либо вызывались без объявления вообще. Все это разрешалось и в первом стандарте С - С89/90. При вызовах таких функций передаваемые аргументы всегда безусловно подвергались default argument promotions, о которых вы сами уже упоминали. В процессе default argument promotions типchar
превращается в типint
и в функцию передается уже именноint
. Вот именно поэтому у "классических" стандартных функций вы никогда не увидите параметров типаchar
, а вместо них будуь параметры типаint
.По этой же самой причине вы никогда не увидите у "классических" стандартных функций параметров типа
short
илиfloat
. Другими словами, в K&R C вообще не было возможности передать параметры типаchar
,short
илиfloat
. Эти типы передавались какint
,int
иdouble
соответственно.А ваши домыслы про влияние типа символьного литерала тут совершенно ни при чем.
С ключевым словами - неверно. В С есть
_Bool
,_Complex
,_Imaginary
и т.д.Что касается
longjmp
то различие между С и С++ тут не настолько сильны, как может показаться. Во-первых, в С менеджмент ресурсов делается рукописным эпилог-кодом. И он точно так же "не выполнится" при выходе из функции поlongjmp
. Во-вторых, в С уже тоже появились [опциональные] неявные конструкторы и деструкторы - это внутренний код, обслуживающий VLA. Именно поэтому описаниюlongjmp
в совокупности с VLA уделено отдельное внимание в стандарте языка.Верно. Дополню, что суть замечания в том, что до C23 все variadic функции в С должны были иметь хотя бы один не-variadic параметр.
Неверно.
Строковые литералы и в С и в С++ являются массивами и имеют тип
char [N]
в языке С иconst char [N]
в языке С++. Независимо от типа, строковые литералы являются немодифицируемыми объектами и в С, и в С++.Уже с самого начала - дичайшее количесто ошибок в статье. Просто ошибка на ошибке.
Что??? Эллипсис в языке С - это именно
...
, то есть действительно variadic arguments. Но объявление c()
никакого отношения к ellipsis или variadic arguments не имеет и никогда не имело. Это т.наз. объявление без прототипа, которое не указывает список параметров. Я не буду расписывать подробно, но замечу, что такое объявление не совместимо с ellipsis-определением. Функции с ellipsis-определением в С, то естьf(...)
, обязаны быть объявлены заранее именно с...
, иначе при вызове такой функции поведение не определено. Это одно из мест "классического" стандартного С, в котором требуется ранее (до первого вызова) объявление функции.Да, такой код является формально корректным из-за того, что функция объявлена без прототипа. Однако поведение такого кода не определено. Вызов функции без прототипа эквивалентен вызову вообще не объявленной функции (в С89/90 такое разрешалось). При вызове функции без прототипа компилятор С обязан "придумать" прототип функции на основе переданных аргументов. В данном случае это будет
f(int, double, char *)
(именноdouble
). Если при этом оказывается, что "придуманный" прототип не совпадает с фактическим определением функции, то проведение не определено. Именно это происходит и в вашем случае - проведение не определено.Это "абсолютно корректен" или нет?
Ключевым различием здесь, которую я уже упомянул выше, является "объявление с прототипом" против "объявления без прототипа". Объявление с
(void)
- это уже объявление с прототипом. Кстати, объявления без прототипа давно являются deprecated и в С23 будут, наконец, формально запрещены.Таблица повторяет вышеуказанную бредятину о том, что
()
- это якобы "эллипсис". Это грубейше неверно.Очередной заряд полнейшей чуши... Функции
printf()
ничего подобного не нужно. Никакой передачи параметров "справа налево" не существует. Это не говоря уже о том, что современные ABI позволяют передавать даже variadic аргументы в регистрах процессора.Во-первых, то, о чем вы писали выше, это и есть "альтернативный стиль K&R". Всякий раз , когда в языке С вы пишете
()
в объявлении или определении функции - это объявление без прототипа, то есть тот самый "альтернативный стиль K&R" - один из его частных случаев. Чтобы уйти от "альтернативного стиля K&R" для функции без параметров мы и пишем(void)
Во-вторых, не "до появления стандарта ANSI C", а и в стандарте ANSI С тоже. Стандарт ANSI С разрешает K&R объявления, хотя там они давно являются deprecated. Стандарт С89/90 разрешал вызов функций вообще без объявления. С99 запретил вызов без объявления, оставив однако возможность делать объявления без прототипа. Только C23 запрещает объявления без прототипа и K&R-стиль в целом. При этом хотя объявления функций c
()
являются K&R объявлениями, в С23 они запрещены не будут - их "сконвертировали" в эквивалент(void)
, то есть все станет в точности как как в С++.Во-первых, default argument promotions состоят из integer promotions плюс преобразование
float
вdouble
. Вы развернули описание, но у вас однако integer promotions описаны не совсем корректно. Ну да ладно.Во-вторых, default argument promotions делаются при передаче не только variadic аргументов, но и при вызове функций без прототипа (именно поэтому в примере выше я указал, что передача аргумента
3.2f
приводит к "придуманному" прототипу с параметромdouble
). Вы это все, конечно, попытались запихать в свою теорию "это всё эллипсис", но это - полная чушь.Никакого отношения к языкам С и С++ это, разумеется, не имеет. Никаких calling conventions ни в С, ни в С++ нет. Что это делает в статье про "особенности С и С++" - не ясно.
Грубейше не верно. Строковый литерал - это массив, то есть
char[N]
в языке С иconst char[N]
в C++."Псевдонимами" в терминологии С и С++ называют идентичные типы, то есть одни и те же типы под разными именами. Так как
char
,signed char
иunsigned char
как вы правильно заметили - это три разных типа, никаких "псевдонимов" тут нет и быть не может. О каком "стандарт не запрещает" вы ведете речь - не ясно.Далее читать не стал...
Это не ошибка, это бессмыслица. Если уж автор воспользовался memcpy, то union ему действительно не нужен. Собственно, потому он и не заметил этой "ошибки", что код прекрасно работает и так. Union с фиктивными полями иногда приходится использовать для, целей выравнивания, но здесь совсем не тот случай. Автор наворотил ненужной фигни с union поверх вполне работоспособной техники и, похоже, до сих пор не понял, что это лишь ненужная фигня.
Использованный в коде
union
содержит полеas_str
. Однако нигде в коде это поле не используется, что говорит о том, что ни это поле никому тут нинафиг не нужно, ни весьunion
никому тут нинафиг не нужен. Зачем код замусорен всем этим? В чем глубинный смысл?Статья содержит кашу из как минимум двух разных типов "малоизвестности": действительно интересных темных уголков языка и банальностей из разряда "я просто ленив и не знаю о существовании С99". Непонятно, зачем было мешать это в одну кучу. Compound literals являются "малоизвестной" фичей языка? Серьезно?
Классика вроде Duff's device - неизвестная возможность? Серьезно? Это редко используемая возможность, но ничего "неизвестного" в ней нет.
"Несуразные объявления указателей" - вообще постоянно и массово используемые объявления. Как это сюда попало?
Про "Совместимые объявления и массивы как параметры функций" написана некорректная ерунда. Язык С накладывает разные требования на объявления одной и той же функции, сделанные в разных единицах трансляции и сделанные в одной и той же единице трансляции. От объявлений в одной единице трансляции требуется полное совпадение типа функции. От объявлений в разных единицах трансляции требуется именно совместимость типов. Подчеркну: совместимость - это именно для объявлений, сделанных в разных единицах трансляции. А в статье приведены последовательные объявления в одной единице трансляции и ведется разговор о совместимости...
В сильно заезженной теме
"a[b]
буквально эквивалентно(a + b)
" допущена явная опечатка. Должно быть*(a + b)
."Одиночный
#
" - это на самом деле интересная тема, которую автор не раскрыл. В стародавние времена, когда проход препроцессора по коду был дорогим удовольствием, существовало соглашение, что исходный файл обрабатывается препроцессором только в том случае, если его первый символ -#
. Как правило директивы#include
стоят в самом начале файла, то есть это соглашение было вполне разумным. Но в некоторых случаях получалось так, что файл не начинался с#
, а обработку препроцессором все-таки нужно было запросить. Вот именно в этом случае в файл первой строкой ставили пустую директиву#
. Сегодня эта возможность уже не актуальна, а пустая директива может использоваться разве что для косметического оформляжа.Ой, спросонья фигню спорол. Беру свои слова обратно. Какой, нафиг,
t
...del