Pull to refresh

Comments 270

Жаль, что в ИТ-мире нет гравитации или массовых вымираний, в первом случае технологии бы падали и разбивались/тонули/улетали в космос (подальше, к Конской Голове, например), а во-втором случае технологии бы вымирали вообще насовсем, как трилобиты, например. Жаль, что Си ещё жив.
Для системного программирования у C маловато альтернатив, зато имеется огромный багаж существующего кода. Непонятно, на что его можно заменить сейчас там, где указатели реальны, а из инструментов отладки только UART и доброе слово?
На ум приходят Ada и Rust, но первый язык так и набрал популярности (хотя он во многих аспектах намного лучше подходит для системного программирования), а второй пока еще довольно молод, но я искренне за него болею.
Можно, конечно, попробовать «причесать» уже имеющийся С (Cyclone, Checked C, etc.) или ограничиться небольшим «безопасным» подмножеством языка (JPL Coding Rules, MISRA C, etc.), но это все мало кто может себе позволить, т.к. программировать с такими ограничениями умеют меньшее количество специалистов, и сама разработка в итоге становится дороже (иногда существенно) и медленее (иногда на порядок).
Иногда из инструментов отладки только 1 светодиод. А иногда нет даже его…
UFO landed and left these words here
UFO landed and left these words here
Писал программки для защищенного режима, в процессе работы отключалась видеокарта, выводил адрес инструкции с ошибкой спикером — два коротких бипа «1», один длинный «0».
C++ же.
Я, наверное, ни за какие деньги не взялся бы писать на чистом С. Это хороший язык, но слишком простой. Я не доверяю софту, написанному на С, потому что понимаю, как сложно писать на этом языке большие системы без ошибок. Это тот же ассемблер, только кросс-платформенный (ну, по большей части).
На самом деле C++ добавляет собственных тараканов к тем, что есть в C. Конечно, если писать на С++ правильно, то код будет намного безопасней. Но если писать на С правильно, то опять же можно бороться со сложностью. Есть куча прекрасных примеров: ядро linux, git, nginx, etc
В ядре linux, на сколько знаю, используют «С с классами», у гита (на зеркале на гитхабе) меньше половины кода на сях, что о многом по моему говорит, nginx по первому взгляду (не изучал особо) и правда на чистых сях, но это скорей исключение и такой проект, как мне кажется, гораздо трудней поддерживать, чем любой ООП. Хотя не исключаю, что у меня ООП головного мозга.
В ядре linux, на сколько знаю, используют «С с классами»


Но как, сэр?
Это же чисто эппловская примочка, да ещё и какая-то высокоуровневая…
Элементарно, сэр. Структура, которая хранит указатели на функции — и вот мы получаем абстрактный объект. Заполняем указатели — и вот у нас экземпляр конкретного класса. Заменяем некоторые указатели на свои, немного магии с container_of и вот у нас наследование.

Ядро Linux объектно-ориентировано, хоть и написано на C. Такие дела.
«Си с классами» — «C with classes» — название первой версии С++, написанной Страусом трупом.
Соотв., Ваше утверждение выглядит странно.
Я уже не говорю о том, что «структура, хранящая указатели на функции» — не обязательно эмулирует классы.
Извините, но «Си с классами» сказал не я. Я просто рассказал как ядро устроенно внутри.

Я уже не говорю о том, что «структура, хранящая указатели на функции» — не обязательно эмулирует классы.

Не обязательно. А ещё для реализации ООП не обязательно иметь ключевое слово class в языке. Более того, язык вообще не может знать про классы. И при этом на нём можно писать объектно-ориентированный код.
Я имел ввиду совсем не то. Прошу прощения, не углублялся в историю и «трупов» не изучал, потому любую имитацию ООП на чистом С называл «С с классами». Больше не буду, еще раз простите.
UFO landed and left these words here
Вы говорите про ObjC, а это совсем другой язык. И еще вам стоит знать, что «компилятор Objective-C входит в GCC и доступен на большинстве основных платформ». То, что это был основной язык программирования (коим возможно остается и сейчас) для устройств фирмы Apple никак не делает его «эппловским».
Да что я опять не так сказал? Вместо минуса лучше бы написали где я не прав, в следующий раз хоть знать буду.
Зависит от задач, как всегда. Если у нас что-то гетерогенное, с базами данных, REST-запросами, гибким логгированием и GUI до кучи, но при этом хочется скорости — надо брать C++ иначе можно рехнуться.
Если у нас есть конкретное очерченная задача (git) — хватит и C. Если нет rich runtime (linux) — опять С. Если мы максимально экономим ресурсы (nginx) — снова С.

Но при этом почти весь хороший код на C всё равно оперирует объектами. Да, там часто проблемы с полиморфизмом и наследованием. Но абстракция данных и инкапсуляция цветет пышным цветом. Достаточно посмотреть API любой сишной библиотеки (кроме libc, естественно).
Если мы максимально экономим ресурсы (nginx) — снова С.

Совершенно неочевидно, почему, кстати.
В ядре активно используются gcc-шные расширения, но «объектно-ориентированные» фишки реализуются вполне себе штатными средствами чистого Си. Никакого особого «Си с классами» там нет:)
Прошу прощения. Я не особо знаю всякие экзотические вещи, такие как названы выше. Под «С с классами» я имел ввиду реализацию ООП на чистых Сях, иногда с использованием GTK и прочих, по моему мнению, извращений. Да, экономия ресурсов и «переносимость», но это неудобно и во многих случаях напоминает как раз написание своего С++, но почему не взять готовый никто отвечать не хочет.

Я не противник С++, но в ветке видел несколько ответов почему в некоторых задачах не взять готовый С++ вместо С:


  • Больший рантайм. Напирмер в андройде libstdc++ есть несколько на выбор Android/Sdk/ndk-bundle/sources/cxx-stl/, подключив любой и хоть немного заюзав — мой apk вырастет. Конечно можно и на урезанном С++ писать, но тут писали якобы таких спецов меньше. Так же если мы скажем что-то оптимизируем и глазами изучаем скажем .map файл, куча мелких с++ функций рантайма начнут мешать.
  • Маленький компилятор, JIT tcc умещается где-то в 100-200кб. Аналогов тут не так много, разве что lua. Так что если мы хотим вставить в тот же apk и не раздуть его, llvm тут не подойдет.
Вы правда смотрите на объем apk и вообще скомпилированного кода?
Может в этом и есть какой-то смысл, но не в ущерб читаемости, удобству и качеству. По крайней мере на тех платформах, где оперируют уже давно гигабайтами.
В ближайший месяц мы выпускаем приложение на Android, в котором часть кода написано на C++ (NDK) с использованием c++_shared (LLVM). Объем библиотек под все платформы вышел на 40-45Мб (7 архитектур), то есть в среднем 6.5Мб на архитектуру. Все приложение весит около 17Мб (apk). Для сравнения то же приложение на iOS весит 15Мб (С и ObjC). Вот уж не вижу смысла бороться ради пары мегабайт.
В плане оперативной памяти все аналогично.

А я и не говорю, что всегда это надо, я сам предпочитаю C++, потому и говорю — в некоторых задачах. А смотреть на размер — да, бывало, было дело сократили размер so до 1.5мб\архитектура (3мб apk, против изначальных ~40мб). А вот приложения по 17мб я и сам не люблю, с нашим то мобильным инетом, а как дела в Китае? А в Индии?


Другое дело что, если это игра, то ресурсы и\или сам код могут занять много больше рантайма и смысла действительно не будет. Но это не значит, что таких задач не бывает, это мы еще не коснулись микроконтроллеров, демосцены, системного\ядерного кода, специфичных кейсов типа портирования c winelib.

Непонятно, в чем преимущество C++ в тех нишах, где позиции С по прежнему сильны: embedded, firmware, kernel core, вот это все. Там не нужны исключения, полиморфизм и RTTI, там нет стандартной библиотеки, там мало толку от шаблонов, там часто вообще нет кучи, т.е. единственный доступный operator new — размещающий, и т.п.
Для «нормального» C++ нужен гораздо более жирный рантайм, а специалистов, умеющих писать на «обрезаном» С++ — намного меньше, чем специалистов по С, т.к. такому «ограниченному» использованию С++ почти нигде не обучают.
Основное преимущество C++ перед C в перечисленных нишах — метапрограммирование.
Наверное, если система очень небольшая и влазит в считаные килобайты, от С++ пользы нет, один вред.Я под такие системы использовал компилятор С++, но писал, по сути, на С. А вот под STM32 d проекте на 5000 строк кода я использовал и шаблоны, и полиморфизм. Можно ли без них? Конечно, можно. Хочу ли я не пользоваться этими средствами? Нет, не хочу, они позволяют мне писать понятный, легко расширяемый и корректный код.
Я как-то делал библиотечку, которая напрямую компилировалась в простые выражения присваивания или чтения битов ввода-вывода.
Ну, типа — там несколько связанных битов, конфигурирующих ввод-вывод и сам I/O pin.
Разумеется, это было несколько инлайновых шаблонов с наследованием — выглядело, в общем, неплохо.
А биты адресовать невозможно (т.е.Си с ними вообще с ума сходил).
Ну и конструкторы-деструкторы — штука няшная.
А биты адресовать невозможно (т.е.Си с ними вообще с ума сходил).

Битовые поля структур не могли бы помочь?

Битовые поля структур точно так же не адресуемы.
Ну т.е. сделать на них тоже можно было — но вряд-ли получилось бы эффективнее.
Плюс необходимость их самому определять «ручками» — а биты определялсь при конфигурировании проекта и выборе контроллера.
А биты адресовать невозможно

std::vector<bool> смотрит на это заявление с недоумением.
Внутри. Это битовый вектор.
Внутри.


А.
Значиццо у нас есть микроконтроллер, для которого есть Си/С++ со специфическим расширением — типом _bit.
Некоторые биты имеют специфическую функциональность — ну там ввод/вывод, настрока пина на вывод, открытый сток/пуш-пул…
И поверх всей этой радости предлагается присобачивать std::vector, который вообще «Не обязательно хранит свои данные в одном непрерывном куске памяти.» (© cppreference)?

Обёртку я тоже нопейсать могу — что я, собственно, там и сделал.
поверх всей этой радости предлагается присобачивать

Нет, std::vector<boot> приведён как широко известный пример техники адресации битов.

И, да, правила.
Это эмуляция битовой адресации, а не.
И да, в треде утверждалось, что «С++ нафик не нужен в эмбеде со своими шаблонами, наследованием и т.д.».
Например, вот tyt.
Ваш тезис ничего не добавляет к моему оппонированию этому.
Попробовал, сделал:
#include <vector>

namespace std {
        void swap(vector<bool>::reference a, vector<bool>::reference b)
        {
                bool t = a;
                a = b;
                b = t;
        }
}

int main()
{
        std::vector<bool> v(2);

        std::swap(v[0], v[1]);
        return 0;
}

Учитывая то, что исключений здесь быть не может, в чём подвох?
А скажите, зачем вы переопределили std::swap?
Готовый std::swap ожидает, что ссылочный тип оканчивается на '&', что, конечно же, не всегда правда. Это же, вроде, известная ловушка шаблонных функций?
В современных компиляторах, поддерживающих C++11 и выше, swap давно уже как перегружен для
vector<bool>::reference
и всё отлично работает.
Да, верно. Перегрузка нужна для С++98. Тем более непонятно, в чём подвох.
а есть и недостатки. Например, подключение библиотек STL, и программа разрастается до таких размеров, что не влазит в контроллер. В итоге придется писать на подмножестве, одинаковом для с и с++.
Метапрограммирование, строгая типизация
Нередко более эффективный код на шаблонах, чем код в лоб, правда ценой раздутия кода. То же самое, конечно, можно сделать на сях, но дольше и с большей вероятностью ошибки.

Язык С практически ничем не отличается от языка С++ в области строгости типизации. Единственное отличие, которое навскидку приходит в голову — это возможность неявной конверсии void * в другие объектные типы указателей в языке С. Все.


Так о какой строгой типизации вы ведете речь?

например, неявный каст const T* к T*
Это откуда? В языке С никогда не было неявного приведения `const T*` к `T*`. Разумеется с того момента, комитет X3J11 ввел в язык С `const`.
Ещё перечисления (преобразующиеся к целочисленным типам, но не обратно), особенно появившиеся в C++11 scoped enumerations (к которым и целочисленные типы не преобразуются).

Явное приведение типов (которое в cast notation преобразует практически что угодно к чему угодно) разбито на static_cast, const_cast и reinterpret_cast (а также имеющий смысл только для C++ dynamic_cast).

Объектно-ориентированным программированием на Си тоже занимаются, реализуя наследование вложением одних структур в другие. Отсутствие встроенных для этого средств приводит к использованию грязных хаков, со статическим контролем типов при этом всё совсем плохо. Например, в Glib Object System такие функции, как g_object_ref/g_object_unref/g_object_new принимают/возвращают просто void*, чтобы не приходилось явно приводить типы; в gtk функции создания виджетов возвращают GtkWidget* (указатель на базовый класс всех виджетов, а не на конкретный класс виджета), и т. д.

Да, перечисления — хороший пример, в дополнение к void *. Еще что нибудь? (Явные касты же — не по теме. Явные касты — это средства обхода контроля типов.)


Заявления о более либеральном контроле типов в С как правило базируются на "пионэрских" верованиях, что С якобы разрешает такие вещи, как int *p = 123 или игнорирование const-корректности (см. яркий образчик такого заявления выше). В качестве доказательства приводится тот факт, что чей-то уютненький компилерчик выдает на это "всего лишь warning".


Понятно, что к реальности такие заявления никакого отношения не имеют.

Явные касты же — не по теме. Явные касты — это средства обхода контроля типов.

Так обходить по-разному можно. (T)v значит вообще практически все проверки выключить, в то время как специализированные касты отключают лишь некоторые проверки (например, static_cast и reinterpret_cast не позволят ненароком сбросить const).

Это прекрасно. Базовые факты о поведении С++-style casts тут все хорошо известны.


Как это относится к теме более строго контроля типов? Контроль типов в языке — это именно контроль неявных преобразований. Явные преобразования (касты) к этой теме не относятся.


В любом случае, "созвездие" С++-style casts позволяет вам сделать все, что угодно (не говоря уже о том, что C-style cast никто в С++ не запрещал).

Как это относится к теме более строго контроля типов?

Приведу пример. Допустим, есть у нас некая функция, которая по каким-то причинам (крайне устаревший код, некомпетентность автора, потенциальное наличие заведомо нереализуемых в данной ситуации особых вариантов поведения и т. д.) принимает параметр типа char*, а не const char*, но аргумент свой, как мы точно знаем, не модифицирует:
int f(char *str);
А нам нужно вызвать её из другой функции, передав аргумент типа const char*. Приходится использовать const_cast:
int g(const char *str, int x)
{
    return x + f(const_cast<char*>(str));
}

Если человек на фоне недосыпа вместо этого случайно написал
int g(const char *str, int x)
{
    return x + f(const_cast<char*>(x));
}
то компилятор ему об этом скажет: const_cast<char*>(x) — невалидная конструкция.

Но в языке Си мы вынуждены использовать cast notation (ничего другого нет) — там аналогичная функция
int g(const char *str, int x)
{
    return x + f((char*)x);
}
с точки зрения компилятора будет полностью валидной.

В одном случае контроль типов сработал, в другом — нет.
В любом случае, «созвездие» С++-style casts позволяет вам сделать все, что угодно (не говоря уже о том, что C-style cast никто в С++ не запрещал).

В любом случае ремни безопасности можно не пристёгивать.

Если у человека есть _цель_ отстрелить себе ногу, то C++-style casts ему действительно ничем не помогут. Но контроль типов — это немного про другое.
Собственно, warning's не имеют никакого отношения к корректности языковых конструкций (соответствия синтаксису).
Это всего лишь дружеская необязательная помощь компилятора.
Таки имеют.

5.1.1.3 Diagnostics
A conforming implementation shall produce at least one
diagnostic message (identified in an implementation-defined
manner) if a preprocessing translation unit or translation
unit contains a violation of any syntax rule or constraint...

В сноске:
...Of course, an implementation is free to produce any
number of diagnostics as long as a valid program is still
correctly translated. It may also successfully translate
an invalid program.

Warning — одна из реализаций «diagnostic message». А успешная компиляция программы допускается даже если последняя некорректна.
Но из этого не следует, что каждый warning приходится на одно такое требование стандарта.

Нет. Это популярное наивное заблуждение.


Во-первых, это было бы справедливо только в отношении компиляторов, которые строго и аккуратно разделяют warnings и errors про указанному вами принципу. Таких компиляторов С просто нет.


Например, при компиляции в GCC в умолчательной конфигурации огромное число warnings — это именно грубейше некорректные языковые конструкции, которые GCC просто так решил отрапортовать как warnings (ради совместимости с каким-нибудь старинным кодом).


Попыткой достижения строгого деления на warnings и errors в GCC является флаг -pedantic-errors, но и он еще не идеален.


Во-вторых, формально придраться к GCC тут нельзя, ибо в С нет понятия warnings и errors. Есть только понятие diagniostic message. Задача компилятора — сказать хоть что-то. А уж разбираться затем в формальной корректности конструкции, листая стандарт языка — это ваша задача.

main()
{
	int *p = 123;
	return 6["Нет"];
}

gcc -std=c99 testc.c
testc.c:1:1: warning: return type defaults to 'int'
main()
^
testc.c: In function 'main':
testc.c:3:11: warning: initialization makes pointer from integer without a cast
int *p = 123;
^


Т.е. оба примера допустимы в С, но недопустимы в С++

Неверно. И main() и int *p = 123; являются грубыми ошибками ("contraint violation") в одинаковой степени и в С, и в С++. Ваш компилятор С выдал требуемые диагностические сообщения, сообщив вам тем самым, что ваша программа не является программой на С и ее поведение не определено языком С. Точка.


Однако видя, что вы пока не умеете "на глаз" отличать "безобидные" диагностические сообщения от серьезных ошибок, я бы посоветовал вам почитать документацию своего компилятора и попросить у него помощи в этом. В частности, в GCC это делается флагом -pedantic-errors. Возьмите в привычку пользоваться этим флагом всегда


main.c:1:1: error: return type defaults to 'int' [-Wimplicit-int]
 main()
 ^~~~
main.c: In function 'main':
main.c:3:11: error: initialization makes pointer from integer without a cast [-Wint-conversion]
  int *p = 123;
           ^~~
Я прекрасно понимаю как оно скомпилируется и как работает.
Например вижу ошибку выхода за границу массива в коде выше, о которой НЕТ варнинга.

И меня такое деление устраивает — error как принципиально не компилируемый код, warning- обратите внимание.

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

Например, вчера у меня после смены версии gcc, в newlib (это такая версия libc, кто не в курсе), появились варнинги (типа char применяется как индекс массива, ай-яй).

И что вы предложите мне выкинуть — платформу, gcc, newlib или %^&%##-pedantic-errors?

Уважаемый, во-первых, то, что вам тут пытаются объяснить, это тот простой факт, что main() и int *p = 123; в языке С формально являются именно принципиально не компилируемым кодом. То, что ваш компилятор этот код проглатывает — это замечательно, но к языку С никакого отношения не имеет. И, соответственно, не имеет никакого отношения к теме этой дискуссии. С таким же успехом вы могли бы рассказывать нам, что там у вас компилируется в компиляторе Фортрана.


Никто вас при этом не призывает прекратить писать такой код — пишите на здоровье что вам заблагорассудится. Но не удивляйтесь потом, что на ваши попытки выдать ваш код за код на С будут смотреть с откровенным нескрываемым недоумением.


Во-вторых, упоминая -pedantic-errors я вам не пытаюсь навязывать никакие привычки. Никто вас не заставляет использовать -pedantic-errors повседневно, тем более, что вы к этому явно еще не готовы. Вы сделали стандартный, уже всем надоевший ляп: попытались доказывать какие-то странные верования о языке С на основе разухабистого поведения неумело сконфигурированного (или вообще не сконфигурированного) компилятора. Но вот перед тем как презентовать свои верования на публику — не помешало бы проверить их с помощью этого флага. Это позволит вам избежать попадания в такие постыдные ситуации в будущем.


В-третьих, если я что вам и навязываю тут — так это сам язык С. Не больше, не меньше. За одиннадцать лет в JTC1/SC22/WG14 у меня выработалась такая привычка.


P.S. К чему здесь вдруг ваши разглагольствования на тему "что вы предложите мне выкинуть" — мне не ясно. Никто вам тут ничего выкидывать пока не предлагал.

Я не знаю, в каком выдуманном мире вы живете, или как трактуете мат.часть С, но этот код прекрасно компилируется и Clang, и MSVC, и самое интересное работает так, как мне и требуется.

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

Это глупая дискуссия между теоретиком и практиком.

О! Уж совсем детские наезды пошли!


Никакого спора тут нет и быть не может. Все эти вопросы уже давным-давно имеют четкие и однозначные ответы. Вы просто по неопытности своей в первый раз эти ответы слышите. Поэтому я даю их вам под запись, а вы — тщательно изучаете и зазубриваете наизусть. Никакого спора.


Что касается вашей феерически "альтернативной" интерпретации работы этих компиляторов — здесь это никому не интересно. Здесь речь идет именно и только о языке С. И все эти компиляторы немедленно откажутся трактовать ваш ошибочный код, как код на С. В противном случае они бы под общий хохот были бы вышвырнуты с рынка смачным пинком под зад.


Что же касается вашей "практики" — это не практика, заблудший вы наш, это что-то вроде "диссертации Василия Ивановича", в которой он, согласно известному анекдоту, оторвав таракану ноги, пришел к выводу, что "безногий таракан не слышит". Или, если угодно, это — изучение творчества группы Битлз по напевам Рабиновича, согласно другому известному анекдоту.


Если вы намерены серьезно изучать и использовать язык С, всю эту вашу "практику" вам придется тщательно стереть из головы — она бесполезна и будет вам только мешать. Придется накапливать с нуля совсем новую практику. А уж когда вы напрактикуетесь хотя бы с половину моего — вот тогда и понимание теории подтянется. Я знаю, ибо сам когда-то таким был.

Как показано ниже со ссылками на первоисточники, стандарта вы тоже не знаете, как и практики.

Потому оставьте весь поучающий пафос себе.

Молодой человек, вы со своими "ссылками не первоисточники" смачно сели в лужу именно так, как садится в нее практически каждый новичок, неправильно понявший 6.3.2.3. Вы у меня уже, наверное, тысячный...


Учитесь не просто читать, а еще и понимать прочитанное. Я вам там все подробно разъяснил, под вашей "ссылкой не первоисточник".


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

Например вижу ошибку выхода за границу массива

То есть вы ещё не перешли на utf-8?
Нет. И если верить стандарту С11, надо было писать u8«Нет»
В цивилизованном мире UTF-8 — кодировка по умолчанию. Так что если вы сохранили исходник в UTF-8, то и результирующая строка в норме должна быть в UTF-8, даже в отсутствие префикса u8 (как минимум, у меня под линуксом в gcc дела обстоят именно так). Это не требуется стандартом (source character set и execution character set, в принципе, могут быть разными), но и не противоречит ему.

Чтобы (достоверно) увидеть ошибку выхода за границы массива в вашем примере, надо знать компилятор и кодировку исходника. Без этой информации отсутствие выхода за границы более вероятно.
В цивилизованном мире UTF-8 — кодировка по умолчанию.
У меня — нет, на гитхабе я тоже встречал в основном ascii.

Полагаться на кодировку исходников — чистой воды непереносимость.

ASCII является валидным UTF-8, если что.

Ваш пример не может быть в ASCII по той простой причине, что в нём есть кириллица. Которой в 7-битной ASCII не предусмотрено:)

Он может быть в ASCII-совместимой кодировке — например, в UTF-8 (весьма вероятно, если вы под Linux-ом) или Windows-1251 (если вы под виндой).
Собственно, поскольку грубую ошибку вы не заметили, а придираетесь к пуговицам, возвращаю вам «что вы пока не умеете „на глаз“ отличать „безобидные“ диагностические сообщения от серьезных ошибок, я бы посоветовал вам» =)

Поведение там определено языком С, хотя и непереносимо (только для второго ворнинга).

Кстати именно буквоедство свойственно многим новичкам — это к посту ниже по некоторых индивидуумов.
Поведение там определено языком С, хотя и непереносимо (только для второго ворнинга).

Нет.


Программа некорректна — содержит constraint violations, т.е. синтаксические или семантические ошибки. Такая программа именно некомпилируема (!) как программа на языке С.


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


И для этого достаточно только main(), не говоря уже о int *p = 123;. Дальше main() уже можно не смотреть.


И не надо пытаться вертеть хвостом с вашим выходом за пределы массива. Он тут не имеет никакого значения. Кстати, выход за пределы массива не является constraint violation, т.е. компиляторы имеют полное право компилировать ваш выход за пределы даже и не пискнув. Диагностики времени компиляции тут не требуется.

Also, in C89, functions returning int may be implicitly declared by the function call operator and function parameters of type int do not have to be declared when using old-style function definitions. (until C99)

Это из http://en.cppreference.com/w/c/language/declarations#Declarators

А про создание указателя из int, я не вижу смысла обсуждать. Конечно это UB, но иногда при работе с железом так и нужно делать. Не запрещено.

Язык С — это язык, описываемый действующей на данный момент спецификацией языка. Язык С описывается спецификацией ISO/IEC 9899:2011, известной в популярной культуре, как С11.


Спецификация С89/90 описывает язык С89/90, а не язык С. Точно также как книга K&R С, описывает язык K&R С, а не язык С.


Создание указателя путем попытки неявного преобразования из int — это с точки зрения языка С не UB, а некомпилируемый код. При работе же с железом, когда это нужно, делается так: int *p = (int *) 123;, но ни в коем случае не int *p = 123;. Язык безусловно требует явного каста для выполнения такого преобразования.

Вот уж нет, приведение указателей на разные типы и конверсия из int это как раз даже не UB, я преувеличил — implementation defined, так написано в 6.3.2.3 пункт 5 стандарта.
5 An integer may be converted to any pointer type. Except as previously specified, the
result is implementation-defined, might not be correctly aligned, might not point to an
entity of the referenced type, and might be a trap representation.
56)

И следуя вашей логике, программы до С11, не являются программами на С.

Вот это уже называется вертеть хвостом, пытаясь апеллировать к последнему стандарту =)

Собственно, статья права — никто не знает С.

Осспади… 6.3.2.3… Каждый пионер проходит один и тот же путь, когда пытается читать стандарт С… (Признайтесь, вы это нагуглили?) Наивное заблуждение о том, что в 6.3.2.3 каким-то образом разрешается неявное преобразование целых чисел в указатели, упорно живет в этих ваших интернетах и отказывается умирать, несмотря на все усилия С-сообщества.


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


Цитата, которую вы привели, объясняет, что будет происходить при вычислении выражения (int *) 123. Цитата, которую вы привели, никаким боком не говорит о том, что в int *p = 123; вдруг магическим образом произойдет неявная конверсия типа int в тип int *. Ничего подобного в вашей цитате не сказано.


А в начале раздела 6.3 ясно сказано


6.3 Conversions
1 Several operators convert operand values from one type to another automatically. This subclause specifies the result required from such an implicit conversion, as well as those that result from a cast operation (an explicit conversion). The list in 6.3.1.8 summarizes the conversions performed by most ordinary operators; it is supplemented as required by the discussion of each operator in 6.5.

Более того, в 6.5.4 Cast operators специально добавлено


3 Conversions that involve pointers, other than where permitted by the constraints of 6.5.16.1, shall be specified by means of an explicit cast.

А дальше вам прямая дорога в описание семантики инициализации, которая в свою очередь отошлет вас к описанию семантику оператора присваивания (а это тот самый 6.5.16 Assignment operators), где ни в коем случае не допускается комбинации из указателя и целого числа в присваивании, за исключением случая, когда справа стоит null pointer constant.


Собственно, статья права — никто не знает С.

Бывает, как видите, еще хуже. Когда человек не только не знает С, а не в состоянии даже толком разобраться в выложенным перед ним описании языка.

Ах, дело лишь в том, что лично вы хотели бы там видеть явное приведение типа, а компиляторы вас не слушают?!

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

И ругнется компилятор варнингом или нет — не суть важно, программа не станет от этого принципиально неверной.

За сим откланиваюсь.
Не слушают? Да что вы говорите? Опять девичьи мечты?

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

И, я вижу, опять начались томные рассказы про то, что «вам нужно»… Я же, как я уже не раз говорил выше, веду речь именно и только о языке С, а не том, что мне или кому-то еще лично нужно.

Ну что ж — и наше вам с кисточкой.
В качестве доказательства приводится тот факт, что чей-то уютненький компилерчик выдает на это «всего лишь warning».

Понятно, что к реальности такие заявления никакого отношения не имеют.

Как минимум, gcc (не такой уж и уютненький, вполне себе серьёзный, по строгой поддержке стандарта в том числе) по умолчанию ведёт себя именно так. Можно и const сбрасывать, и вообще совершенно разные объектные типы неявно приводить друг к другу — будет только предупреждение, а при компиляции C++-программ — ошибка.

Diagnostic message есть — формально требование Стандарта со стороны компилятора соблюдено.

С тем что формально требование стандарта соблюдено никто не спорит. В данной подветке речь идет не об этом, а о том о том, что делать выводы о корректности программы на основе того, как некий компилятор делит свои диагностические сообщения на errors и warnings — занятие совершенно бессмысленное. Тем не менее некоторые индивидуумы этого не понимают.


Это деление в случае GCC — не более чем "уютненькая" фантазия авторов компилятора, обилием каковых фантазий компилятор GCC широко знаменит. И подрастающее студенчество, формальных документов не читавшее, из-за этой "уютности" страдает заметно, как показывают в том числе и комментарии в этой ветке.


Когда-то нам стоило огромных усилий пробить разработку в направлении "педантичного" деления на ошибки и предупреждения в GCC (пресловутый -pedantic-errors), Почему это направление разработки вызвало такое сопротивление в GNU — мне до сих пор не ясно.

Авторы С подтверждают — язык С был придуман для того, чтобы Ричи и Кернигану не надо было напрягаться программированием на ассемблере. Цитировать не буду, но этот момент обозначен в их книге.

Надо заметить, что Брайан Керниган не имеет никакого отношения к разработке языка С вообще, как он сам не раз заявлял. Как активный участник разработки языка В (предшественника С) он участвовал в подготовке книги, но не более того.

Это во-первых. А во-вторых, на тот язык, который Денис Ритчи разработал «для замены ассемблера», можно взглянуть в известном документе самого Дениса Ритчи: «C Reference Manual» (CRM)

http://www.math.utah.edu/computing/compilers/c/Ritchie-CReferenceManual.pdf

В этом языке действительно чувствуется сильное влияние ассемблера. Однако все это было выкинуто из языка очень быстро: CRM C очень сильно отличается от K&R C, не говоря уже о ANSI C, не говоря уже о современном С.
Обеспечение переносимости при разработке с C++ — отдельная головная боль.
То есть вы сосредоточены не только на решении задачи, вам нужно еще и с компилятором побороться.

С++ не так просто скомпилировать на разных версиях операционной системы даже при использовании одного и того же компилятора (не говоря уже про разные компиляторы и разные платформы и разные стандарты С++).

Например,
https://github.com/cocaine/cocaine-core
Это одна из базовых технологий Яндекса, сделанная не самыми глупыми программистами.
Я разрабатываю кросс-платформенные приложения под Винду, Линукс, Андрои, иОС и Мак. Ваше заявление, мягко говоря, не соответствует действительности. Яндекс где-то накосячил.
Это можно — кто ж спорит.

Но это требует серьезных ДОПОЛНИТЕЛЬНЫХ затрат или просто СПЕЦИАЛЬНЫХ знаний.

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

Ну не должен язык, претендующий на базовый, быть таким ненадежно-непредсказуемым.

Чтобы было понятно о чем речь и что проблема решаема в принципе — сравните, сколь небольшими усилиями делается переносимая программа на Python или Go.

С С++ не сравнить — это просто небо и земля.

В Python и Go если разработчику и нужно с чем то бороться при этом — то с отдельными аспектами не POSIX API (Windows). Что как раз ожидаемо и логично.

С С++ дополнительно приходится бороться с различиями в компиляторах и библиотеках — даже в пределах ОДНОЙ платформы.

P.S.:
Яндекс приведен как контора, которая может позволить себе разработчиков уровня выше среднего. И если даже они косячат со своими базовыми вещами (эта штука, что я привел — занимается запуском и мониторингом контейнеров с другим ПО в кластере Яндекса) — то что же ожидать от основной массы?

Не должен быть язык таким. Это плохое свойство. Существенный недостаток. Никак он не может претендовать на системный, базовый.
Я не помню, когда последний раз за 6 лет кросс-платформенной разработки «боролся с различиями в компиляторах и библиотеках». ЧЯДНТ?
Но я уделяю внимание использованию стандартного С++ и всячески избегаю любых платформозависимых расширений. "-pedantic-errors", /W4, периодический статический анализ и всё такое.
Возьмите и откопилируйте программу по ссылке выше.

Да, там кое-где код написано не универсально. Но дело не только в этом.

Дело не только в СТАНДАРТНОМ С++. Там еще куча ЗАВИСИМОСТЕЙ.

Все от вас зависит только в ПРОСТЕЙШЕМ ПО, которое вы пишете сами.

Как только начинаете использовать внешние вещи (библиотеки) — количество сюрпризов увеличивается.

Вплоть до багов в такой казалось бы базово-универсальной вещи как Boost, не говоря уже о более специфичных библиотеках…
Бибилиотек у меня около 15, от exiv2 до libusb и libftdi. Естественно, используем только кросс-платформенные библиотеки.
Буст не использую, он слишком большой и сложный в интеграции.
Алогичность детектед:

Средний программист С++ (коих подавляющее большинство) понятия не имеет как писать 100% переносимый код.

Вы же приводите в пример просто «историю успеха».

Никто не спорит, что при желании на С++ можно писать переносимо.

Совсем другой вопрос — а какими это достигается усилиями, по сравнению с более естественными в этом отношении системами программирования. Ну например, Python и Go и Java…

Я работаю в маленькой фирме и ничего не знаю про среднего программиста. Я знаю, что пришёл работать после ВУЗа с нулевым опытом, методом тыка стал разбираться с портированием уже имевшегося когда на другие платформы, и быстро научился любить и уважать стандарт С++. А потом почти так же быстро научил любить стандарт сотрудников и начальника. В том числе и с помощью -pedantic-errors.
Как только начинаешь писать под больше, чем один компилятор и/или одну платформу — очень быстро дисциплинируешься и всё встаёт на свои места.

А ваши слова про подавляющее большинство — пустой звук, не подкреплённый никакими доказательствами.

На Go и Питоне уже можно писать GUI, или по-прежнему как всегда? Питон вообще не рассматриваю как что-то большее, чем средство написания скриптов для автоматизации каких-то относительно небольших процессов. Java в целом хороша, но жирный рантайм, кривой UI и производительность «как повезёт». Это моё ИМХО, your mileage may vary.
Вы вновь повторяете алогичность.

Ваш частный случай — это всего лишь ваш частный случай.

Факты таковы, что большая часть людей даже ни разу не стремятся профессионально рости. Это касается любой профессии, а не только профессии программиста.

И, к сожалению, создатели третьих библиотек не всегда уважают стандарт и не всегда проверяют кросс-платформенность.

Я уже привел пример Яндекса, который может себе позволить нанять специалиста заметно выше среднего (и дело не только в деньгах, они могут позволить себе нанять специалиста даже потратив меньше, потому как все прекрасно понимают, что после Яндекса — все пути тебе открыты).

И тем не менее — видим огромные косяки. Там даже не так просто скомпилировать под Ubuntu другой версии…

А ведь это не какой то локальный продукт — их Cocaine базис облака Яндекс и распространяется свободно для любого желающего.

Для того, чтобы убедиться, что большинство С++ создают продукты такого же качества — достаточно побольше взять открытых проектов и попытаться их скомпилировать (даже не запустить!!! ) на различных системах.

Если я скажу что я делал это — и все очень часто плохо — это же не будет для вас доказательством? Вы можете это понять только самостоятельно.

Проблема с GUI решаема.
Совсем не обязательно создавать монолит. GUI (если у вас не нечто требовательное до 3D, к примеру) — вполне безболезненно может существовать отдельно от вычислительной части.

И, очень интересно, а что вы называете кросс-платформенностью своей системы? Где именно возможна ее работа, на каких ОС?
1. Факты таковы, что вы бросаетесь общими незначащими фразами, не подкреплёнными доказательствами, выдавая их за факты. Пример: «большая часть людей… не стремятся профессионально расти».
2. Вы аппелируете к авторитету Яндекса, но для меня это не авторитет.
3. В репозитории есть README. Там принято писать список поддерживаемых платформ и компиляторов. Ожидать беспроблемной сборки на других системах не стоит. Есть спецификация, там написано, что система гарантирует. Всё остальное вам никто не обещал, так чего вы ожидаете? Вы, наверное, из той категории людей, которые ставят приложению в Google Play негативные отзывы за то, что приложение не умеет ещё и кофе варить.

О чём мы вообще спорим? На С++ можно писать непортируемый код? Да, можно. И на Java можно. Банально, вызвали WinAPI функцию — всё, ваш код стал непортируемым. На С++ вызывать сторонние компоненты проще, и для меня это преимущество, а не недостаток. Правильное использование фич всегда на совести програмиста, а не языка.
Зато писать портируемый код на современном С++ намного проще, чем на С (кроме самых embedded платформ, наверное).
4. GUI на основе другой технологии — лишняя головная боль. Всякие прослойки-адаптеры., через которые надо правильно протащить каждый чих.
5. Я уже говорил, что почти все мои программы работают на Linux, Windows и OS X, а некоторые из них также на Android и iOS.
Доказательством может быть только опыт.
Лично вы работали в окружении опытных спецов — вам очень повезло.
Нет, не так. ВАМ ОЧЕНЬ СИЛЬНО ПОВЕЗЛО, ЕСЛИ ВОКРУГ ВАС ОДНИ ПРОФИ.
В реальной жизни это большая и большая редкость.
В среднем человек любой квалификации довольно посредственнен как профессионал.
То, что вы еще не сталкивались с огромным числом не профи — говорит о вашем малом жизненном опыте.
И только.
Если бы вы внимательно прочитали мой комментарий на один уровень выше, вы бы увидели, что я работал в окружении обычных хороших програмистов, которые тоже понаписывали непортируемого кода, но жизнь, не без моей помощи, наставила их на путь истинный.
> что я работал в окружении обычных хороших програмистов, которые тоже понаписывали непортируемого кода

Тем самым вы и подтвердили мое утверждение.

> но жизнь, не без моей помощи, наставила их на путь истинный.

Разумеется, кто же будет делать лишнюю работу в здравом уме и твердой памяти?

С C++ этой работы слишком много, вот в чем дело.
>Тем самым вы и подтвердили мое утверждение.
Какое? Что можно написать непортируемый код? Так я ещё два комментария назад сказал, что это так, но это не аргумент.

>C C++ этой работы слишком много, вот в чем дело.
Я так не считаю. Единственная серьёзная сложность была — избавиться от анонимных union.
> Я так не считаю. Единственная серьёзная сложность была — избавиться от анонимных union.

В вашем собственном коде.

А как же про сторонние библиотеки? Повезло?

Во многих других языках данная проблема просто и не может возникнуть. В других языках ты как правило борешься всего то с особенностями программной платформы (например Windows vs Unix).

В случае C++ — плюс еще и с самим языком.
Это не дело.

Не должно от «анонимных union» ничего зависеть. Это бага дизайна языка
Нет. Это бага компилятора, что он компилит нестандартный код.
Библиотеки изначально брались только кросс-платформенные. Никакого везения, холодный расчёт.
В мире С++ компиляторы, частично поддерживающих тот или иной стандарт или поддерживающих нестандарт — никого не удивляют.

С другими языками таких разночтений — огромная редкость.

Как можно на столь зыбком фундаменте жить.
Это не совершенно непроизводственные затраты.

Вместо того, чтобы сосредоточиться на задаче — ты тратить часть своего времени на борьбу со своим инструментом.
Это не алогичность. Человек доказывает возможность применения языка в этой области, и это вполне можно делать своим примером.

А что до качества среднего программиста — что мне до этого, если я про свой уровень всё знаю и могу позволить себе выбирать место работы так, чтобы коллеги тоже были выше среднего?
Никто не спорит что С++ можно применять.

Мы обсуждаем статью — у С++ проблем не меньше, чем у С. Он не самый предсказуемый язык, не самый лучший для переносимости.

Требующей дополнительной квалификации и внимания для написания действительно переносимых программ.

По сравнению с Go, Java, Python…
там, где надо писать на с/с++, уже не подойдут питон, го и джава
На C++?
Правда там ответ на эти вопросы будет таким же.
За такие комментарии надо с HABRы выпиливать.

Почему нет? Законы эволюции вполне действуют и на языки программирования. Паскаль почти вымер. Кобол почти вымер. А Си — слишком хорош, чтобы его выкинуть.

Си — слишком укоренился, чтобы его выкинуть.
А не слишком хорош.

Как минимум без Си исчезают все распространенные операционные системы, приличная доля компиляторов/интерпретаторов, туева хуча утилит и базовых библиотек без которых не сможет жить или создаваться софт более высокого уровня и пр. и пр.
Чем си не хорош-то? Хоть сам на него редко сажаюсь, но его лаконичность и простота каждый раз действуют на мозг очень приятно после какой-нибудь джавки. Наверное, лучше него с KISS ничто не сочетается, но это сугубое ИМХО
А огромного количества UB вам мало?
UB легко избегать. Особенно, если знаешь, что пишешь код под определенную архитектуру с определенной ОС, тогда его будет не больше, чем в любом другом языке
Си — это такой над-ассемблер. Таким его задумали, так надо было. И до тех пор пока на Си писали ядра операционных систем да драйвера — эти самые UB можно пережить.

Но когда речь идет о прикладном ПО, то планка совсем иная.

UB в Си побольше, чем в «любом языке», используемом для написания прикладного ПО, если в качестве любого рассматривать Python, Java, Go.

Если под «любым языком» вы понимаете C++ — тогда конечно, да, я с вами соглашусь.
Как я уже писал, это исключительно мое мнение, но большая часть UB, которое встречал — это работа с многопоточностью и асинхронностью. И в этом Си не сильно хуже других языков, иногда даже лучше

Плюсы для меня темный перегруженный лес, так сложилось исторически, поэтому сравнивать с ними не хочу и не берусь)

Ну я бы не сказал, что в Python, Ruby, Perl намного меньше undefined behaviors. Все они основаны на Си-рантайме и также в основном исповедуют типа без привязки к разрядности архитектуры. Хотя есть и типы более жестко определенные, но тем не менее. И это только один из аспектов.


Например, большая часть языков основана сейчас на LLVM, GCC или JVM, и вынуждены следовать их UB.

В языке их нет. Это просто определения в библиотеке, описываемые под известный компилятор и известные платформы.


Если компилятор, например, не определит макросы, по которым stdlib догадается о размерности базовых типов, то "всё пропало". А он, по стандарту, и не обязан это делать.

Вроде как stdint.h входит в C99 https://ru.wikipedia.org/wiki/Stdint.h. Соответственно можно проверить, есть ли uint16_t на данной платформе, если нет, то uint_least16_t обязан быть по стандарту всегда.

uint_least16_t имеет совсем иное (не столь строго определённое) поведение. в частности, при битовых сдвигах.

Ну выше было что в стандарте нет, тем не менее в стандарте он все же есть, его может не быть на платформе и стандарт это предусматривает. Можно написать корректный код без UB, который работает и в случае наличия точного типа и когда его нет. Можно или 2 варианта кода или универсальный, который закладывается только на least. Так же в случае использования точного типа и его отсутствия — код просто не соберется. Так что все же имхо Antervis прав.
Добавлю ещё, что для стандартных типов тоже есть определённые гарантии (если уж мы вынуждены писать на с89), следующие из описания хедера <limits.h>:
  • char, signed char, unsigned char — не меньше 8 бит (в точности — CHAR_BIT бит), нет битов-заполнителей;
  • short, unsigned short — не меньше 16 значащих бит;
  • int, unsigned int — не меньше 16 значащих бит;
  • long, unsigned long — не меньше 32 значащих бит;
  • long long, unsigned long long — не меньше 64 значащих бит (но эти типы появились лишь в C99).
Лаконичность и простота — это вам в хаскель, а не си.
Главная проблема Си для меня — отсутствие классов = нет простого и понятного механизма управления сложностью проекта через абстракции.
Я привык заменять классы отдельными для структур .h файлами, состоящими из, собственно, структуры и функций. Расширять их можно уже в .c файлах, импортирующих соответсвующие заголовочные, получая таким образом что-то вроде наследования и контрактов. По удобству это совсем не то, конечно, но уже приятнее
Зря вас минусуют. Я разрабатываю на С и С++ уже около 15 лет, начиная от микроконтроллеров и заканчивая сложными распределенными системами — и могу сказать — да, жаль.
И нет, я не предлагаю перейти всем на javascript:) Но необходимость создания универсального языка программирования для замены С и С++ назрела уже давно. А не происходит это из-за инертности мышления и так называемого груза унаследованного кода, который якобы никто не хочет переписывать. Господа, переписать его не такая уж и проблема, было бы на что. Программисты так или иначе постоянно переписывают код, адаптируют его, переписали бы и на единую стандартизированную модификацию Си, заодно кучу древних багов выловили бы. Ах да, бизнес же не хочет тратить лишнее время и платить лишних денег за непонятную работу… им же надо чтобы тяп ляп и в продакшен. Ничего, не обеднели бы.

Если не углубляться в тонкости, сам язык Си вполне хорош за исключением некоторых мелочей. В нем есть некоторые странные соглашения, которые не мешало бы отменить ради универсальности (имя массива это указатель на первый элемент массива — в результате массивы это не first class objects) и некоторые древние и опасные наросты (препроцессор, #define true false, инклуды и т.п.). В нем нет многих современных фич — что плохо. И в нем есть неопределенное поведение (примеры которого показаны в статье) — что очень плохо. Не так уж и сложно жестко прописать правила выравнивания структур или размеры базовых типов. Но нет — с каких-то там древних времен, когда о стандартизации никто не задумывался и писали компиляторы на коленке для себя, получилось что под какую-то архитектуру размеры базовых типов оказались нестандартные. И может сейчас и архитектуры-то этой нет, но тягомотина с undefined behaviour продолжает тянуться ради дурацкой совместимости.
Позвольте мне только один нескромный вопрос: а возможно ли в принципе свести воедино все зоопарки устройств, под которые писали и пишут на си, в один единый строгий стандарт?

Все дырки из поста, они же являются не прихотью, а компромиссом. Эти дырки — не слабоумие, а чёткое понимание, что в одних устройствах есть возможность контролировать условия работы и порядок выполнения, а в других — нет. Что в одних устройствах дорого выделить маленький кусок памяти, а в других вообще все типы одного 8-битного размера, а процессор не может в умножение. Даже, казалось бы, явные прорехи, вроде неопределённости порядка выполнения операндов — компромисс по отношению к параллельным системам, в которых вычисление левой и правой части происходило бы одновременно в автономных контекстах с результатом «1». Действительно, переменная же не помечена разделяемой, почему нет, openMP бы так и сделал (если бы мы умудрились распараллелить эту строку).

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

Единственное бесспорное место — макросы, заменить бы их более удобоваримым метаинструментом.
Возможно.
Если все типы одного 8-битного размера, то это не значит что нельзя сделать 16-битные типы из них. Просто программист будет знать, что 16-битные типы не нативные. Подобная ситуация есть с FPU: на некоторых микроконтроллерах FPU отсутствует, но никто не мешает объявить тип float или double. Другое дело что работать это будет медленно и нередко криво.
Более того, можно ввести понятие модульных возможностей языка. И подключать или отключать различные языковые модули в свойствах проекта (а к таковым я бы отнес floating point, длинную арифметику (GMP), исключения (кстати различных видов — SEH, DWARF, SJLJ и возможно какие-то еще), RTTI, различную рефлексию, динамическое выделение памяти, сборку мусора, многопоточность и т.д.). Если модуль используется в коде, но отключен в проекте — ошибка компиляции; или подключайте модуль, или переписывайте код так чтобы данные языковые возможности не использовались, либо пишите свой модуль (что тоже потенциально возможно).
Вы же понимаете, что вы не избавляетесь от проблемы, а перекладываете её в другой карман. Я не спорю, в стандарте явно не хватает встроенных инструментов получения некоторых гарантий, хоть он и сделал не так давно приятный шаг вперёд, как с теми же типами.
Но, позвольте, разве модульность не устанавливается на уровне компилятора? Тот же AVR GCC проглатывает далеко не весь код, хотя бы по причинам организационным, 32 килобайта, все дела. Все ваши модули уже спрятаны в нём. в компиляторе, и иначе быть не может, а доступ к нестандартным методам всё равно получается после подключения платформозависимых заголовочников, к которым есть ассемблерный код. То есть вы ни капли не добавите ничего нового.

То, что вы предлагаете, оно должно продумываться на этапе разработки библиотеки. Конечно, есть проблема сужающихся возможностей, когда наши амбиции придавливает sizeof(int), но ведь есть ровно обратные ситуации. Это вопрос говнокода, не языка. Но если мы строим библиотеку, способную работать на разных архитектурах, то нам над ними придётся воспарить в некоторой, возможно. параметризуемой, абстракции. А иначе как?
Все верно, параметризируемая абстракция должна быть.
В простейшем случае если нам нужен просто int и все равно сколько там байт, так и пишем int. А если нужно что-то конкретное (для сетевого пакета или двоичного файла) то пишем int8 или int16 или int32.
Но параметризация должна быть явная, как и модульность. Вообще все должно быть явное, стандартизированное и общее для всех компиляторов.
Мне очень интересно посмотреть на возможный синтаксис и, пожалуй, способ получения\расширения модулей. Собственно, механизм работы, как бы это могло быть. Вам не составит труда?
За 40 лет проблему не решили. Хотя ей занимались люди поумнее и вас и меня.

Уже не решится проблема. Очевидно.
Легко.
Компилятор, зависимый от платформы, который не должен пропускать подозрительные вещи.

В Си же можно накосячить «втихую».
Во-первых, модифицированные компиляторы и так не пропускают подозрительные вещи.
Во-вторых, а что считать подозрительным? Подозрительно что-то может быть с точки зрения платформы, конкретного компилятора, но мы же определяем независимый от предрассудков язык…

Да даже порядок вычисления параметров метода, его неопределённость подозрительна, только если мы вычисляем их один за другим. А если все сразу параллельно, считая глобальные переменные разделяемыми между контекстами, а локальные — в зависимости от погоды на Марсе?

Конечно, случайно взятая библиотека в таких условиях не соберётся сразу без проблем. Но модификация библиотеки по отметкам компилятора/анализатора/отладчика «здесь может быть гонка, здесь буду синхронизировать» — куда проще, чем писать тот же SQLite с нуля только потому, что наше подмножество языка ну очень уж далеко уехало от их подмножества.
Си был создан для замены ассемблера при создании операционной системы.

Сейчас же его применяют и для прикладного ПО, где спускаться на столь низкий уровень нет необходимости (тем более сегодняшние компиляторы очень хорошо оптимизируют).

Но Си все так же остается всё таким же «ассемблерно-близким», хотя для большей части продуктов, создаваемых с его помощью — таких вольностей не требуется, они только увеличивают риски косяков…

Зачем мы пишем прикладное ПО на «почти ассемблере» до сих пор?
Вы пишите? Если да, то вопрос риторический. Если нет, то, позвольте, в чей огород камень? В огород библиотечников? Но те же libusb и прочие — системное ПО, да и SQLite не особо выше уровнем, 90% сорцов — попытка натянуть сову производительности на глобус универсальности. И, посмотрите, очень даже неплохо получается. А статья — она именно о железячнике, который писал то самое системное ПО с его системными заморочками реального времени.

Или вы из «новой волны», которая пишет под Arduino на JavaScript?
Если Вам не нравятся существующие языки — пожалуйста, создавайте свой язык. Затем создайте компиляторы языка для всех более-менее распространённых платформ; ну или сделайте язык настолько хорошим, что другие программисты заходят создать компиляторы. Не забудьте добиться высокой эффективности программ на этом языке на любой платформе. И затем убедите программистов использовать Ваш язык.
Как видите — совершенно ничего сложного; раз-два, и готово.

Создатели и последующие развиватели C пошли немного совсем другим путём: они знали, что есть процессоры разной архитектуры, иногда — совершенно дикой с т.з. людей, привыкших к i*86 и ARM. Поэтому в C было определено поведение программ в определённых случаях. И было чётко сказано, что за пределами этой области поведение программы м.б. всяким-разным.

Например, случай:
x = 1 << y;
правильно работает при y>=0. А при отрицательных y поведение не определено. Если Вы хотите, чтобы для отрицательных y выполнялось
x = 1 >> (-y);
то никто не мешает Вам написать проверку y на знак и выбрать соответствующее действие.

Более того: прямо на этапе компиляции можно проверить, как данная архитектура (процессор и компилятор) ведёт себя для данного случая. И можно отказаться компилироваться на «плохой» архитектуре. Или для «плохой» архитектуры можно сделать менее эффективный, зато точно работающий код.
в 4 вопросе не хватает ответа «42», так как блока с вопросом там нет, видимо, это вопрос жизни, вселенной и всего такого…
Чукча не читатель, чукча писатель
Вопрос 4 порадовал:
4

А. 0
В. 1
С. 16
D. Я не знаю
А я на него правильно ответил =)
Он видимо для этого и задумывался. Чтобы счет был хотя бы 1 из 5.
Откуда операция сравнения даст 4 ???
Все там верно
На мой взгляд, для некоторых вопросов возможны более точные ответы:

1) А, В и С возможны (в зависимости от конкретных размеров типов).
2) A или В (потому что == возвращает или 0 или 1). Но не С (потому что == не может вернуть 2, стандарт это явно оговаривает)
3) А, B или С возможны, в зависимости от размера char и от его знаковости. Но если копнуть глубже, то тут вариант «Не знаю» действительно подходит лучше, потому что стандарт С не оговаривает кодировку символов! И код пробела, вообще-то, может быть любым (стандарт только обязывает его влезать в 5 бит).
4) Вопроса нет, поэтому я действительно не знаю.
5) Это не «я не знаю», это называется «неопределенное поведение».

Еще пара моментов:
— Почему-то паддинг в структурах вы называете «отступы» — никогда не слышал такого применения, обычно отступ — это отступ в начале строки исходного кода. Но допустим, с русскоязычной терминологией у меня плохо.
— «Более того, размер типа char в битах не определен.» Вообще-то, он определен в макросе CHAR_BITS. Вы, видимо, имели в виду, что он не определен в стандарте — это так.
5) Это не «я не знаю», это называется «неопределенное поведение».

Вопрос был не «что это», а «что вернёт программа».
опрос скорее о том «знаете ли вы стандарт».
на практике мы работаем на определенных платформах и компиляторах, и их поведение нам известно.
итого. верные ответы должны быть такими:
1)зависит от окружения и настроек выравнивания.
2)зависит от окружения.
3,4)опять же зависит от окружения, либо UB.
5)UB.

в общем-то таких примеров можно массу подобрать

получился опрос для школьников… с понурыми ответами у доски вида «нууу я не знаю...»

надеялся на что-то более серьезное, а не про сферических коней в вакууме.
Так это не понурые «нуу я не знаю...», а уверенные — «я не знаю, потому что».

Или такое же уверенное — на платформе под которую я пишу, с моими настройками на моем компиляторе в первом вопросе ответ 8, во втором 0, в последнем 2 и т.д.

Какой простой тест, я на все вопросы верно ответил как оказалось.
Великая месса в С миноре Вольфганга Амадеуса Моцарта. Да, Моцарт тоже писал на С.

Вас обманули, причем дважды. Во-первых, по-русски это называется до-минор. Надеюсь, на языке "До" Моцарт не писал. Во-вторых, в латинизированной нотации до-минор — это c (прописная буква), а C (заглавная буква) — это до-мажор.


Бонус: похожий на букву C знак в начале каждого нотного стана (после ключевых знаков) — это разорванная окружность, обозначающая размер 4/4 (и происходящая из мензуральной нотации, где она означала несовершенный, т.е. четно-дольный метр).


(Ну и еще по-русски это называется "Большая месса")

Я бы не взял программиста который такие трюки будет использовать в реальном коде.
Видимо поэтому вам никогда не доведется принимать такие решения.

Это не трюки, а фундаментальные свойства С, как платформы. Это атомы, из которых состоит С. Абсолютно любая программа на языке С опирается на эти «трюки».
Нет. Ни одна программа не должна опираться на неопределенное поведение
В моем комментарии нет ни слова о том, что программы «должны опираться на неопределенное поведение».
все эти трюки являются примерами неопределенного поведения. Или, как минимум, неоднозначно/контекстуально определенного/платформозависимого поведения.

Во-первых, это оголтелое вранье. Никакого неопределенного поведения в первом и втором примерах нет. Третий и четвертый смогут содержать его потенциально, но речь там идет не о том. Явному неопределенному поведению посвящен только пятый пример.


Во-вторых, темой этой дискуссии является избежание неопределенного поведения, а не некое "опирание" на него. Я по-моему ясно это постулировал в комментариях выше. Внимательнее читаем комментарии.

вы написали:
Абсолютно любая программа на языке С опирается на эти «трюки».

Часть «трюков» — примеры неопределенного поведения, программы без ошибок его не содержат. Остальная часть — неоднозначности, закладываться на которые в общем случае нельзя.
Есть мнение, что при написании критически важных вещей первичным будет знание не стандарта, а целевых компилятора и железа, ибо разработчики оных могли забить на стандарт.
Ну, много проблем не с тем, что разработчики забивают на стандарт, а с тем, что они реализуют по-разному то, что не регламентируется жестко стандартом.
Если использовать только то, что описывает стандарт без всяких(пусть и общеупотребительных) трюков, то вероятность что-то сломать гораздо меньше, а если и сломается — всегда можно попенять разработчикам компилятора, что они неправильно реализовывают стандарт.
Стандарт Си в области целочисленной арифметики — это большая боль. Он слишком поздно появился для такого важного языка.
В этом направлении боли не видел я, кроме тривиального 3/2.

Есть другие прорехи, конечно.
Сначала воспринял ответ «Я не знаю», как шуточный заведомо неверный, типа сдался. «Не определено» aka UB тут больше подходит. Как только это понял — на все ответил верно.
Варианты ответов составлены плохо, а именно «Я не знаю». Я трактовал его как признание, что я не знаю С. Правильно было бы написать «Неопределенное поведение». Тогда результаты ответов были бы другими.

Там не всегда «неопределённое поведение». «Неопределённое поведение» там только в последнем вопросе и, возможно, в четвёртом (зависит от размера int), в остальных «определяемое реализацией» (implementation-defined).

это не меняет сути моей притензии
Ctrl+F «Итак верные ответы»
В последнем случае на самом деле всё понятно
Рассмотрим два пути:

1. правый операнд при сложении вычисляется первым
Тогда наше выражение пройдёт следующий путь
> i = 0;
> 0++ + ++0 -> 1++ + 1 -> 2 (i = 2)

2. левый операнд вычисляется первым
> i = 0;
> 0++ + ++0 -> 0 + ++1 -> 2 (i = 2)

и результат равен 2, и значение i равно 2
В зависимости от компилятора (хотя могут все и вычислять одинаково, но не обязаны). Это неопределенное поведение по стандарту «Если программа пытается модифицировать одну переменную дважды не пересекая точку следования, то это ведет к undefined behavior. Так говорит Стандарт.» http://alenacpp.blogspot.com/2005/11/sequence-points.html
Это говорит о том что Ваше утверждение не обязательно верное, точнее результат выполнения программы такой, но только сегодня, и только в этой версии компилятора (может еще в других). Сегодня такой код выдает 2. А завтра благодаря side-effects оптимизаций компилятора он может выдать и 1 и 3 (а может еще что-то). Разработчики компиляторов не обязаны делать так чтобы результат получался 2. И если это будет выгодно в угоду другим оптимизациям они изменят логику и результат будет другой.
1
(i++) + (++i) =(компилятор параллельного мультискаляного процессора считает что можно вычислить результат скобок независимо, а потом сложить) = 0 + 1 = 1
Как получить три, не придумал, но в более сложных и запутанных ситуациях может получится и больше разных ответов. Причем они все не будут багами, потому что UB.
Еще процитирую ту же статью на которую дал ссылку:
Вот пример такой баги: Bug13403. Их таких там очень много.

В следующей программе
#include int main(){
int i=0;
i = 6+ i++ + 2000;
std::cout << i << std::endl;
return 0;
}

Результат единица! А должен быть 2006.
Если убрать «i++», то результат правильный («2006»). Но пока есть «i++» внутри выражения, я могу делить, умножать, вычитать, складывать, все равно результат всегда «1».
Например в выражении
i = (6+ i++ + 2000)/2;
«i» все равно единица.

Но если я заменю постфикс «i++» префиксом "++i" тогда все считается корректно.

Ну отвечают на это все — «это undefined behavior по Стандарту» и баг закрывают.
Это потому что автор неправильно объяснил причину. В C есть понятие точек следования. Если между выражениями A и B имеется точка следования, это гарантирует, что все side-effects, связанные с выражением A, уже произошли, но ни один side-effect, связанный с выражением B еще не произошел.

Также в стандарте написано, что если у вас есть два side-effect'а, изменяющие один и тот же объект, и между ними нет точек следования, то у вас undefined behavior.

Скрытый текст
Строго говоря я немного упростил, дословно там написано так:

If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.

В этом примере мы имеем два изменения результирующего объекта за одну точку следования (так как ни оператор +, ни префиксный/постфиксный операторы ++ точек следования в себе не содержат), следовательно имеем UB.
А вот GCC 4.2 (если не ошибаюсь) с -03 выдавал 1. Объясняю на пальцах.

Логика такая: компилятор принял i за константу внутри точки следования и оптимизировал левый инкремент до возврата нуля, а сам инкремент поставил в конец точки следования (и отбросил за ненадобностью, ведь дальше тела функции i не используется). После чего выполнил правый инкремент, сложение и вернул результат.
Это не более чем частный случай проявления неопределенного поведения.
Существуют такие расширения как GCC атрибуты aligned и packed которые позволяют вам получить некоторый контроль над этим процессом, но они не стандартизированные.

Чё это они не стандартизированы? Уже лет 5 как.

C11 ISO Standard, 6.7.5 Alignment specifier.
April 2011.
Все UB не от скудоумия авторов стандарта, а от стремления избежания лишних действий в рантайме.
Хотя можно было бы и разработать несколько подмножеств стандарта и, соответственно, режимов компиляции. Например «легкий» — то что сейчас и «строгий» — рантайм из-за проверок утяжеляется, но UB минимизируются
Ответил «я не знаю» на первые два вопроса — интуитивно отгадал правильный ответ на остальные три, даже не читая вопросов =) Итого 5.
Ваш тест пройдет даже моя бабушка!
Я бы рискнул сделать ставку на то, что 99% бабушек смогут пройти этот тест!
Вывод: все проблемы от излишней уверенности, нанимайте бабушек!
Вапще когда я вижу int, short, long и прочее подобное у меня уже автоматически возникает вопрос: а архитектура то какая?

В целом для своего времени не привязывать типы к размерам, возможно, было неплохой идеей. Ибо бывали платформы и 16-битные, и 32-битные, и у каждого типа процессора были свои оптимальные размеры для целочисленных инструкций, которые разработчик процессора мог рекомендовать автору компилятора — это делало программы быстрее, причём, с теми рабочими частотами, ощутимо быстрее.

Что до UB по любому странному поводу — дык, не с проста. Каждое описанное UB это зарытый где-то рядом повод для оптимизации. В условиях нехватки процессорного времени тоже отличное решение.

Беда только в том, что теперь процессорного времени на человека есть целая гора, это позволяет нам запускать тяжеленные софтины на прожорливой Java, обрабатывать запросы к веб-серверам на интерпретируемых языках и делать много других странных вещей, недопустимых во времена создания C. Возможно, стоит сместить фокус системного программирования с оптимальности на безопасность?
Очевидно что Intel. Эта зараза от туда тянется.
Я уже давно привык юзать конкретные чёткие типы, как например uint32_t, int8_t и так далее.
Для математики тоже есть свои типы с чёткими размерами, от float64_t до float32_t, мелочь не используется -потому как считается в тиках намного дольше.

Насчёт выравнивания структур при оптимизации. Самый простой способ — выполнить объединение двух заведомо соразмерных структур. Для запрета перемешивания — объединение с перехлёстом.
Подобные знания требуются программисту который пишет на уровне железа — это конкретное обращение к регистрам в памяти. Всем остальным — лишний багаж знаний.

Это хорошо что ещё не затронули тему передачи параметров в функцию. Для разных архитектур ядра — параметры могут передаваться самым удивительным способом. С лева на право, с право налево, через регистры ядра, через теневые регистры ядра (не знали? :) ), через стек, через внешнюю память.
Зоопарк.

Архитектура ядра оказывает влияние на calling convention, но это не единственный определяющий фактор. ОС (если есть) или компилятор (если нет) могут из каких‐то соображений выбрать не самое оптимальное соглашение, которому будут следовать. (В принципе, компилятор может даже проигнорировать соглашение о способе вызова, принятое в определённой ОС, но по понятным причинам (взаимодействие с чужими библиотеками), скорее всего, не будет этого делать.)

Или наоборот явно будет использовать несовместимый cc при использовании какого-нибудь stdcall или, например, cilkplus. Calling conventions, например, могут зависеть от набора инструкций или выбранного float-abi.

не забывайте, что есть научный софт, софт для анализа больших объемов данных, как пример из собственной практики могу привести обработку СВЧ радиосигналов. Там очень важно, чтобы работало как можно быстрее.
Для программиста на С нет ответа «Я не знаю». Правильный ответ — на моей платформе (ОС+компилятор) ответ будет конкретно таким и таким.

Хотя я и ошибся с promote to int во втором примере )
На 4-й пример GCC 4.2 -O3 выдает ответ 1, а GCC 4.8 с любой оптимизацией выдает 2.

Все такие исключительные ситуации для своей платформы и компилятора и его версии не запомнить (и не узнать) коммент в этом топике — см цитату.

Я раньше пользовался… К примеру переполнением uint8_t для счетчика циклического буфера чтобы избавится от всяких if, но чем больше узнаю, тем больше удивляюсь.jpg и стараюсь так не делать.
Наверное имелся в виду 5й. Там правильный ответ — так делать не надо, UB.

Но опять же, есть определенный перечень UB, на которые ты знаешь, как будет у тебя, но все равно сознательно пишешь нормально.
Я раньше пользовался… К примеру переполнением uint8_t для счетчика циклического буфера чтобы избавится от всяких if

А почему перестали? У uint8_t никаких проблем быть не должно.
Переполнение, кажется самым популярным используемым UB с ожидаемым результатом. И хотя «разработчики компиляторов не звери» и так работает процессор (контроллер), но на всякий случай. Переполнение это UB. А если поведение UB может меняться от версии к версии gcc, то на всякий случай лучше не надо.

Andrey2008 из PVS-Studio утверждает с переполнением лучше не шутить
Обычно глупости выглядят как-то так (собирательный образ):

Ну да, формально переполнение 'int' приводит к неопределенному повреждению. Но это не более чем болтовня. На практике, всегда можно сказать что получится.
(Хотя в статье проблема в неправильном типе, и переполнение работает так как ожидается. И речь в статье не про UB при переполнении, но думаю у него опыта и знаний больше чем у меня.)

Можете считать это религией или стилем. Но мне кажется он хорош. Если нет острой необходимости, то не использую хаки (и goto).
Переполнение, кажется самым популярным используемым UB с ожидаемым результатом. И хотя «разработчики компиляторов не звери» и так работает процессор (контроллер), но на всякий случай. Переполнение это UB. А если поведение UB может меняться от версии к версии gcc, то на всякий случай лучше не надо.


Переполнение uint8_t, равно как и любого другого беззнакового типа, согласно стандартам вообще невозможно, и, соответственно, не является UB.

См. C89 3.1.2.5 Types, или C99 6.2.5:9 Types, или C11 6.2.5:9 Types
A computation involving unsigned operands can
never overflow, because a result that cannot be represented by the
resulting unsigned integer type is reduced modulo the number that is
one greater than the largest value that can be represented by the
resulting unsigned integer type.

Очень зря. Свойства uint8_t (если таковой тип предоставляется реализацией) четко оговорены спецификацией языка и никакой неоднозначности не допускают. "Зацикленное" (модульное) поведение беззнаковых типов — очень полезная фича языка, пользоваться которой можно и нужно. Поэтому ваше "чем больше узнаю", похоже, привело лишь к тому, что с водой вы выплеснули и ребенка.

Спасибо Вам и jcmvbkbc. Был неправ. «Переполнение» беззнаковых типов безопасно и всегда будет одинаково. Я этого не знал и сильно удивился, чего это вдруг знаковое переполнение — UB, а беззнаковое безопасно. Пытался понять читая стандарт и форумы — не дало много толку, переполнение не только «implementation dependent» как могло бы быть, но и «undefined behavior», а беззнаковое определено. Хотя стандарт написан прямо юридическим языком, трудно понять (а причины и подавно). Картинка удивляюсь.jpg все еще актуальна.

Ну в любом случае — если не знаешь можно перестраховаться, а знаешь четкую границу — можно и делать финт ушами и быть уверенным в безопасности. Спасибо.
На все вопросы ответ: «Зависит от платформы и компилятора».
А первый вопрос любят на собеседованиях спрашивать, только, конечно, пишут, какое выравнивание установлено.
Не знаю С. Ответил на все вопросы правильно.
Я тоже самое хотел написать. Слово в слово.
Когда смотрел вопросы, в голове был ответ «зависит от платформы и/или реализации компилятора».

Когда смотрел предлагаемые ответы, среди них не было правильного. Потому что ответ «Я не знаю» в данном случае не отражает реалий.

Поэтому это не тест, а подвох из серии «На грядке сидело 3 воробья, одного поймала кошка, сколько воробьёв осталось?».

То, что в С куча тёмных, платформозависимых и компиляторозависимых моментов, совершенно не удивляет, а удивляет, что автор за 15 лет об этом не догадывался…
Но даже так, мы не сравниваем типы, мы сравниваем размеры. И единственная гарантия которую стандарт дает о размерах short int и int в том, что предшествующий не должен быть больше последующего.

В добавок к этому: оператор sizeof возвращает не размер операнда, а его пропорциональное (или как это правильно сказать) отношение к размеру char. То есть sizeof(int) == 4 еще не означает что int равен четырем байтам. Это означает только что в int может вместиться четыре char'а (Wikipedia).
по стандарту sizeof(char) = 1
Да, всегда. Вот только количество битов в char не обязательно должно быть равно восьми.
потому что количество бит в байте не обязано быть равно 8
То есть sizeof(int) == 4 еще не означает что int равен четырем байтам


Тут два момента.

С одной стороны, означает:
The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type.


С другой стороны, стандарт Си описывает всё в терминах абстрактной машины, не привязываясь ни к каким свойствам реальных машин. В Си своя модель памяти, которая не обязана сколько-нибудь явно соответствовать организации памяти той реальной среды, в которой программа будет выполняться (собственно, и не соответствует: компиляторы одни переменные помещают в регистры, другие полностью выкидывают, переупорядочивают операции чтения/записи и т. д.). Поэтому и понятие «байт» в стандарте Си, строго говоря, не обязано совпадать с понятием байта в «железе».
На самом деле, по теме статьи можно расширить свой список «я этого не знал» на сайтике тестов http://www.quizful.net.

Так сколько то в день попыток бесплатных, хватит чтобы оценить уровень пола.
Я все-таки считаю, что язык программирования должен быть в первую очередь инструментом достижения цели.
Хорошо, если этот инструмент будет удобным в работе. Тогда и результат будет быстрее, и, возможно, качественнее.
Как правило, современным инструментом работать намного эффективнее. Вместо лучковой пилы лучше взять электролобзик. И не нужно думать, как раньше, над углом заточки зубцов пилы, затачивать их вручную. Сейчас просто идешь в магазин, покупаешь пилку «по дереву», вставляешь в лобзик, и пилишь со скоростью отряда пильщиков, с точностью ювелира.

Так и С. К черту все эти undefined behaviour, #pragma pack, ++i++ + i++, sizeof(**&(*i)->j), const * const * int.
Нужно заниматься делом! Придумывать стартапы, накидать на Ruby бэкэнд с интеграцией в соцсети, мобильное приложение под все телефоны. И не забыть добавить Bluetooth, потому что с ним всегда лучше.
2016 год уже заканчивается…
Пока вы там сверху придумываете стартапы и накидываете бэкэнд на Руби, разработчики на С разрабатывают вашу пилу и решают проблему с медведями. Это тоже дело, и оно должно быть сделано прежде, чем на вашей системе вообще можно будет запустить Руби и соединить ее с интернетом, в любом году.
Это само собой. Но Winsock была написана сто лет назад. Работа с файлами тоже. Все, это больше не надо писать. Используйте результат труда тех людей, кто писал на С. Но сами пишите на более высокоуровневых языках. Иначе не видать нам технологической сингулярности, как своих ушей!

Единственное место, где стоит писать на С сейчас — это микроконтроллер ATTiny13A. Дешевый, маленький. 1КБ Flash, 64 байта RAM. Для остального С++ или еще более высокоуровневые языки.
Для мелких микроконтроллеров стоит писать на ассемблере, тогда вы больше напишете, быстрее напишете, быстрее отладите то что написали и будете точно знать что происходит с вашим кодом в проце.
Ассемблер у каждого микроконтроллера разный. Лучше использовать универсальный ассемблер — язык С.
Поверьте, поддерживать старый проект на С намного проще, чем на ассемблере.
Но чистый С использовать уже не имеет смысла. Компиляторы поддерживают С++, некоторые даже С++11. И не обязательно использовать динамическое выделение памяти, порой очень не хватает простого синтаксического сахара. Например, в С меня жутко выбешивает необходимость объявлять все переменные строго в начале функции. Какого черта я не могу написать for(int i=0; i < c; ++i)?
а разве это требование не было убрано в стандарте с89?
Может быть, но Freescale CodeWarrior IDE не в курсе этого.

Во-первых, при чем здеcь "IDE" не совсем ясно. Все таки это определяется компилятором, а не IDE.


Во-вторых, вы фантазируете. В CodeWarrior нет и никогда не было требования "объявлять все переменные строго в начале функции".

В с99 так можно, в с89 — нельзя.

В языке С нет и никогда не было требования объявлять переменные строго в начале функции. Откуда вы это вязли?


Даже K&R С (не говоря уже о стандартизованном С) переменные всегда объявлялись в начале блока, а не в начале функции. Однако и это — далекое прошлое. В языке С переменные уже 17 лет как можно объявлять где угодно.


Другими словами вас "жутко выбешивает" вам же выдуманная чепуха, не имеющая никакого отношения к реальности.

Я под него вполне успешно писал на плюсах, кстати.
Это всё прекрасно, только стандартный руби до сих пор на шее С сидит и в обозримом будущем с него слезать вообще не собирается :) Как, впрочем, и некоторые другие интерпретируемые языки. А делами нужно заниматься, да, только каждый своим — кто руби, кто явой, а кто и си. Главное в гармонии сосуществовать :)
Да, но твоя пила, стул и ты, все написано на С =)
да вам в жизни не понадобится #pragma pack в том, что можно написать на питоне/джаве/руби. Остальное является головоломками и/или примерами ub, хотя в реальном коде встречается раз в декаду и то скорее в виде ошибки.
чесно, не понимаю где может применяться код выложенный в вопросах…
разве что для терапии в специализированной психиатрической клинике для айтишников.
Ну, например, вот в таких вариантах: https://github.com/cocos2d/cocos2d-x/blob/v3/cocos/renderer/CCTexture2D.cpp#L131 (Вопрос к публике, а вы с какого раза въехали в порядок операций в выражении?). Добавить инкремент по другую сторону присваивания, и будет практически вариант за номером 5.

А что-то вроде ((void (*)(int))8)(42) довольно часто случается в микроконтроллерах.
Про https://github.com/cocos2d/cocos2d-x/blob/v3/cocos/renderer/CCTexture2D.cpp#L131 очень примитивный подкол.
Это стандартное обращение, начиная с 1го класса изучения С: *pc++=c;
С большим интересом прочел статью и все комметы (на данный момент). ИМХО сделано много верных замечаний про зоопарки (платформ, систем, компиляторов), сказано много правильных слов про необходимость знать стандарты языка, особенности железа и компилятора, на котором работаешь. Но как быть с переносом кода? Где-то, когда-то, кто-то на антикварных сейчас железе, системе, компиляторе (ЖСК) написал нужный мне код, но я могу не знать то ЖСК, тогда вероятно, что используя тот код отгребу кучу багов, которые мне трудно будет фиксить. Чем дальше в лес — тем больше дров: зоопарк ЖСК растет. Для долговременной преемственности кода нужен достаточно строгий язык типа стандартного Паскаля. Пусть ОО Паскаля. Конечно, за все приходится платить. Си гораздо более гибкий чем Паскаль, и даже турбо, и даже Дельфи. Многие трюки будут невозможны. Но м.б. стоит заплатить отказом от трюков? А когда они очень нужны, то тогда и только тогда использовать Си и/или ассемблер?
>Си гораздо более гибкий чем Паскаль, и даже турбо, и даже Дельфи.
В целом, это неверное утверждение.

>Но м.б. стоит заплатить отказом от трюков
Уже заплатили. Уже поколение, которое не знает трюков, на полном серьезе утверждает, что Ява или даже питон, быстрее чем С.
Мозгами заплатили.
И деньгами постоянно платим за непомерно жрущие ресурсы программы телефонов, телевизоров, итд
Использование С/С++ тоже не всегда способствует экономии ресурсов.
Нпр., недаром появление общедоступных многоядерных CPU вызвало появление TBB, которое не очень экономично по скорости.
Потому что TBB вынуждено быть универсальным паттерном, а в отдельных приложениях специальные паттерны могут быть быстрее.
Платим только потому,
что дешевле купить железо,
чем заплатить программистам за переделку, за доведение до ума, за тестирование и за выпуск более совершенного железа.

Экономика — она прагматична.
Она выбирает более дешевый путь.

В стародавние времена — когда железо было несопоставимо с трудом программистов дорогое — тогда и были нужны все эти трюки, край как нужны.
А не потому ли, что экономике выгоднее _продать_ еще раз зайца в новой одежке?
При этом лично ты как программист получаешь деньги. И профессия программиста не самая низкооплачиваемая.

Так что это выгодно тебе как программисту. Выгодно лично тебе.
А не какой-то там абстрактной экономике.
а теперь представьте лишние затраты на электроэнергию, связанные с неэффективными алгоритмами.
Ты не понял что такое «практичная экономика»

Голые абсолютные цифры — не важны. Какими бы страшными они не были.

Имеют значение только цифры в сравнении: «если мы пойдем таким путем — будут такие затраты, а если мы пойдем другим путем — будут иные затраты.»

Если бы затраты электроэнергии были бы выше, чем зарплата программистов — программистов бы заставили оптимизировать.

В этом и смысл саморегуляции в экономике.

Если бы затраты электроэнергии были бы выше, чем зарплата программистов — программистов бы заставили оптимизировать.

В таком виде это могло бы работать только в отношении продуктов «для внутреннего пользования». Ключевая проблема: энергонеэффективное ПО пишут ваши программисты, а платят за электроэнергию пользователи. Поэтому вы наблюдаете классическую «трагедию общин»: хотя более оптимизированное ПО могло бы снизить расходы на электричество и покупку новых ЭВМ (любого вида), результат вы увидите только если многие производители ПО начнут делать так же: достаточно многие, чтобы новый компьютер (не думаю, что ошибусь, сказав, что пользователи не имеют привычки определять, какое ПО жрёт энергию, если только это не пользователи техники, работающей от аккумуляторов) не требовался последней версии всех программ, которыми пользуется большинство пользователей.


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

Вы просто престарелый фокусник, который не заметил, как алхимия разделилась на разные науки.

Так и будете дальше жонглировать символами пунктуации, пока с одной стороны нормальные люди напишут vhdl-схему и скомпилируют rt-код прямо в железо, а с другой стороны толпа юннатов напишет за три дня такой интерфейс для юзеров, что вам и не снилось, ни по срокам ни по качеству.

А пока в вашем т.н. простом языке т.н. программирования ловят undefined behavior светодиодами, на других простых языках, типа Оберона пишут операционные системы для того чипа, что чуть выше описали в vhdl-схеме таким кодом и с такими подходами, которые понятны пятикласснику. Страшовато, если пятиклассники поймут, что никакой магии нет.

О боже мой, в мире столько архитектур, и только язык Си может покрыть их все? Да хер там, нет никакого языка Си, нет никакой обещанной кроссплатформенности, есть только лицемерие и затыкание ртов. Жаль, что эволюции в ИТ нет, и она не отправила вас в Верхнюю Тундру.
Я не утверждаю, что надо все писать на С, я лишь сказал, что трюки знать надо (да, пятиклассникам).

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

>на других простых языках, типа Оберона…
На Обероне и прочих языках (c#, d, rust,...) вполне себе можно писать системы — см.выше про Паскаль. Но пока что мы все пользуемся теми, что написаны на С.

>О боже мой, в мире столько архитектур, и только язык Си может покрыть их все
Да, Си есть практически везде. Потому что на нем строится все остальное.

>Жаль, что эволюции в ИТ нет, и она не отправила вас в Верхнюю Тундру
Я там был, там нефть и газ добывают. И софт там совсем не на Яве ))))
Подавляющее большинство любителей сишарперов и джав НЕ представляют себе что делает в компе их программа в действительности. Доказательство — малое количество неглючных программ на этих языках. Зато быстро в разработке, пусть и жрёт ресурсы — не свои же они, а пользователей… Вы только представьте себе, сколько электричества ВПУСТУЮ сжирает каждый запуск долбаного софта на сишарпе (компиляция псевдокода) в масштабах планеты!!!
А пример кода от Яндекс по ссылке для компиляции… вы же не знаете наверняка, что код не был написан СПЕЦИАЛЬНО так, чтобы не компилироваться где попало. Или есть доказательство обратного? Нету — значит пример совершенно не подходит. А в результате — все выводы на этом примере построенные — ошибочны.
По Яндексу:

Это ПО управления кластером контейнеров. В этих контейнерах запускается много чего уже прикладного яндексовского.

Работает это нормально с какой-то из несвежих версий Ubuntu (не помню с какой точно).

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

Вы утверждаете, что яндексовцы намерено заложили невозможность эксплуатации своего кластера на обновленных версиях все той же Ubuntu? Зачем?
при чем тут вообще ubuntu? На запускаемость программ на с++ на линуксе влияют в первую очередь версия libstdc++, во вторую — версия ядра.
Речь о https://github.com/cocaine/cocaine-core

Вы не в теме совершенно, если считаете, что сложный софт имеет столь незначительную зависимость как libstdc++.

От ядра там минимальная зависимость, компилироваться не хочет вовсе не из-за ядра.

Хорош теоретизировать — скомпилируйте хотя бы на свежем Debian, он ближайший родственник Ubuntu.

Речь о том, что от языка (точнее gcc реализации) зависит только libstdc++, которая уже 10 лет как backward compatible, всё остальное — стороннее ПО. Не компилится код яндекса или еще чей-то там? Язык то тут при чем?
Под языком я подразумеваю всю инфраструктуру использования языка — включая библиотеки и компиляторы и синтаксис и различные стандарты…

Корневой дефект языкой инфраструктуры С++ — она допускает создание кода, к которому необходимо приложить еще дополнительных усилий, чтобы обеспечить переносимость.

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

Борьба со своим собственным инструментом — вот это и плохо.

Если бы не было других языков, где подобных проблем даже близко нет (к примеру, Go), я бы не стал называть это корневым дефектом языка.

Если интересно почему именно, то попробуйте самостоятельно скопилировать.

Меня не нужно спрашивать — я уже не помню точно на чем оно спотыкалось. Но помню, что не в одном и даже не в трех местах проблемы. Проблем непереносимости (даже на соседнюю версию Ubuntu) там — множество.
Пардонте, вы выше голосовали за python, java и иже с ними, но… А как там зоопарк у питона? Я уже могу скачать .py файл и запустить его, или мне нужно проверить, это второй или третий? Как там у интерпретаторов второго фланга с совместимостью? Я могу скачать openBLAS и запустить его на любом интерпретаторе? Нет?
А как там у Java дела, оракловские машины виртуальные не сильно расходятся показаниях? Классы на PC и Android ведут себя одинаково?
А голанг, он уже вышел за приделы уютных x86-64 в народ? Он уже породил собственные системы окружения, или всё ещё «хороший-симпатичный», но чуть что — линкует плюсовые библиотеки и такой весь не при делах? «Это не мои ошибки, я не причём!»

Вы меня прям убиваете своим портированием. Назовите мне способ написать независимую от операционной системы прослойку для обращения к функционалу, специфичному для конкретной системы? Нет его.
На GoLang ещё не написали своих WxWidgets или Qt, а как только напишут, появятся такие же возмущения, что программа не собирается на разных версиях ОС, что ведёт себя криво и тп. Либо так, либо он навсегда останется любимой принцессой фриков, на которую можно только вожделенно смотреть, и которой из-за этого суждено умереть девственницей.
Без конкретных примеров сложно даже понять в чем суть проблемы. Обычно когда я пытаюсь написать что-то непереносимое, это начинается либо с -std=gnu++11/14, либо с #include <windows.h>
Хорош теоретизировать — скомпилируйте хотя бы на свежем Debian, он ближайший родственник Ubuntu.

Скомпилировал интереса ради, Debian 8 stable. Заняло 10 минут — 3 на то чтобы установить недостающие хедеры, 7 на то чтобы собственно скомпилировать, откачав по пути в swap 2Г данных из памяти.
Лог сборки
[0][jcmvbkbc@octofox build]$ cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=`pwd`/root…
— The C compiler identification is GNU 4.9.2
— The CXX compiler identification is GNU 4.9.2
— Check for working C compiler: /usr/bin/cc
— Check for working C compiler: /usr/bin/cc — works
— Detecting C compiler ABI info
— Detecting C compiler ABI info — done
— Check for working CXX compiler: /usr/bin/c++
— Check for working CXX compiler: /usr/bin/c++ — works
— Detecting CXX compiler ABI info
— Detecting CXX compiler ABI info — done
— Found libbfd: /usr/lib/libbfd.so
— Boost version: 1.55.0
— Found the following Boost libraries:
— filesystem
— program_options
— system
— thread
— Found libltdl: /usr/lib/x86_64-linux-gnu/libltdl.so
— Found libmsgpack: /usr/lib/libmsgpack.so
— Found libmhash: /usr/lib/x86_64-linux-gnu/libmhash.so
— Found libuuid: /usr/lib/x86_64-linux-gnu/libuuid.so
— Performing Test HAVE_CXX_FLAG_STD_CXX11
— Performing Test HAVE_CXX_FLAG_STD_CXX11 — Success
— Performing Test HAVE_CXX_FLAG_WALL
— Performing Test HAVE_CXX_FLAG_WALL — Success
— Performing Test HAVE_CXX_FLAG_WEXTRA
— Performing Test HAVE_CXX_FLAG_WEXTRA — Success
— Performing Test HAVE_CXX_FLAG_WERROR
— Performing Test HAVE_CXX_FLAG_WERROR — Success
— Performing Test HAVE_CXX_FLAG_WOVERLOADED_VIRTUAL
— Performing Test HAVE_CXX_FLAG_WOVERLOADED_VIRTUAL — Success
— Performing Test HAVE_CXX_FLAG_PEDANTIC
— Performing Test HAVE_CXX_FLAG_PEDANTIC — Success
— Performing Test HAVE_CXX_FLAG_PEDANTIC_ERRORS
— Performing Test HAVE_CXX_FLAG_PEDANTIC_ERRORS — Success
— Configuring done
— Generating done
— Build files have been written to: /home/jcmvbkbc/tmp/toster/cocaine/cocaine-core/build
[0][jcmvbkbc@octofox build]$ time nice make
Scanning dependencies of target cocaine-core
[ 3%] Building CXX object CMakeFiles/cocaine-core.dir/src/actor.cpp.o
[ 6%] Building CXX object CMakeFiles/cocaine-core.dir/src/actor_unix.cpp.o
[ 9%] Building CXX object CMakeFiles/cocaine-core.dir/src/api.cpp.o
[ 12%] Building CXX object CMakeFiles/cocaine-core.dir/src/chamber.cpp.o
[ 16%] Building CXX object CMakeFiles/cocaine-core.dir/src/cluster/multicast.cpp.o
[ 19%] Building CXX object CMakeFiles/cocaine-core.dir/src/cluster/predefine.cpp.o
[ 22%] Building CXX object CMakeFiles/cocaine-core.dir/src/context.cpp.o
[ 25%] Building CXX object CMakeFiles/cocaine-core.dir/src/context/config.cpp.o
[ 29%] Building CXX object CMakeFiles/cocaine-core.dir/src/context/mapper.cpp.o
[ 32%] Building CXX object CMakeFiles/cocaine-core.dir/src/crypto.cpp.o
[ 35%] Building CXX object CMakeFiles/cocaine-core.dir/src/defaults.cpp.o
[ 38%] Building CXX object CMakeFiles/cocaine-core.dir/src/dispatch.cpp.o
[ 41%] Building CXX object CMakeFiles/cocaine-core.dir/src/dynamic.cpp.o
[ 45%] Building CXX object CMakeFiles/cocaine-core.dir/src/engine.cpp.o
[ 48%] Building CXX object CMakeFiles/cocaine-core.dir/src/errors.cpp.o
[ 51%] Building CXX object CMakeFiles/cocaine-core.dir/src/essentials.cpp.o
[ 54%] Building CXX object CMakeFiles/cocaine-core.dir/src/gateway/adhoc.cpp.o
[ 58%] Building CXX object CMakeFiles/cocaine-core.dir/src/header.cpp.o
[ 61%] Building CXX object CMakeFiles/cocaine-core.dir/src/logging.cpp.o
[ 64%] Building CXX object CMakeFiles/cocaine-core.dir/src/repository.cpp.o
[ 67%] Building CXX object CMakeFiles/cocaine-core.dir/src/service/locator.cpp.o
[ 70%] Building CXX object CMakeFiles/cocaine-core.dir/src/service/locator/routing.cpp.o
[ 74%] Building CXX object CMakeFiles/cocaine-core.dir/src/service/logging.cpp.o
[ 77%] Building CXX object CMakeFiles/cocaine-core.dir/src/service/storage.cpp.o
[ 80%] Building CXX object CMakeFiles/cocaine-core.dir/src/session.cpp.o
[ 83%] Building CXX object CMakeFiles/cocaine-core.dir/src/storage/files.cpp.o
[ 87%] Building CXX object CMakeFiles/cocaine-core.dir/src/trace.cpp.o
[ 90%] Building CXX object CMakeFiles/cocaine-core.dir/src/unique_id.cpp.o
Linking CXX shared library libcocaine-core.so
[ 90%] Built target cocaine-core
Scanning dependencies of target cocaine-runtime
[ 93%] Building CXX object CMakeFiles/cocaine-runtime.dir/src/runtime/logging.cpp.o
[ 96%] Building CXX object CMakeFiles/cocaine-runtime.dir/src/runtime/pid_file.cpp.o
[100%] Building CXX object CMakeFiles/cocaine-runtime.dir/src/runtime/runtime.cpp.o
Linking CXX executable cocaine-runtime
[100%] Built target cocaine-runtime

real 7m43.287s
user 5m54.896s
sys 0m14.752s
[0][jcmvbkbc@octofox build]$ uname -a
Linux octofox.metropolis 3.16.0-4-amd64 #1 SMP Debian 3.16.36-1+deb8u2 (2016-10-19) x86_64 GNU/Linux
[0][jcmvbkbc@octofox build]$ cat /etc/debian_version
8.6

ЧЯДНТ?
Я утверждаю, что я не знаю мотивов яндексовцев на то или иное действие, а по сему допускаю возможность. И откуда вы знаете, что у них работает именно этот код? Слишком много вариантов! Вы же ни того ни другого не знаете, и не докажете. А причин может быть гораздо больше.
Я просто спросил знакомых ребят из Яндекса, они как раз имеют касательство к эксплуатации этого кода.

У них работает именно этот код. И именно на Ubuntu (версию уже не помню).

Завязывай беспочвенно фантазировать — твое бесполезное словоблудие не интересно.
Вы только представьте себе, сколько электричества ВПУСТУЮ сжирает каждый запуск долбаного софта на сишарпе (компиляция псевдокода) в масштабах планеты!!!

Сколько?


(да, кстати, а сколько электричества впустую жрет компиляция никогда не использованных методов в языках без JIT? сколько теряется на недостаточной оптимизации без знания call path?)

По ходу я лучше всех знаю Cи. Прочитав только первый вопрос, я уже знал, что на все вопросы будет ответ «не знаю». Остальные вопросы я прочитал просто из любопытства.
насколько я знаю, размер char фиксирован стандартом, а int'ом с размером 4 может быть литерал типа 'a' в си. (не с++)
Ширина типа 'char' — то есть количество значащих битов в нем — не оговаривается стандартом языка. Результат же 'sizeof(char)' — гарантированно 1. То есть тип 'char' — это единица измерения размеров всех остальных типов, это «байт» в понимании языка С. Однако диапазон значений этого типа не фиксирован.

Оговаривается минимальное значение бит: во‐первых, char должен вмещать все символы из основного набора, при чём все эти символы должны представляться положительными числами независимо от знака char (C99, 6.2.5/3). Во‐вторых, размер типа в битах должен быть не меньше 8 (C99, 5.2.4.2.1).


Также в 5.2.4.2.1 оговаривается минимальный диапазон значений: [-127, 127] для signed char, [0, 255] для unsigned char; границы диапазонов допустимо раздвигать, но не сужать.

а кто нибудь может привести примеры использования подобных абракодабр в своем (чужом) коде? из всех приведенных примеров как то сомнительно их осмысленное применение.
осмысленное применение ub? Я даже не могу представить почему это звучит сомнительно…
main(){return 3[«Нет»];}

Перед вами — абстрактные дистилированные примеры, иллюстрирующие фундаментальные свойства языка. Как таковые, эти свойства языка используются в абсолютно всех программах на языке С без исключения.

Проведу аналогию с электроникой. В книжке «Искусство схемотехники» после описания соответствующего элемента приводится два набора примеров «Удачные схемы» и «Негодные схемы». И те и те отражают «возможности» и самое главное — реализуют некую задумку (подписано что эта схема делает и для чего предназначена), только вторые показывают как делать не надо.
А тут смысла применения конструкций подобных 1-5 вообще не видно.

Вы по прежнему не понимаете, о чем речь. Конструкции 1-5 в таком чистом виде не имеют никакого применения. Они лишь демонстрируют важные фундаментальные факты и языке. На эти фундаментальные факты опираются все программы на С без исключения. Это не "схемы", удачные или неудачные, это иллюстрации Закона Ома.

Относительно задания с


main(){
  char a = ‘ ‘ * 13;
  return a;
}

: здесь не только уже обсуждённые проблемы вроде


  • Не определённый размер типа в битах.
  • Не определённый в стандарте код пробела (Vim до сих пор содержит код, который призван компилироваться в системе с EBCDIC в качестве кодировки, и там точно есть такая проблема).

Есть ещё одна: main() может и имеет тип int, но реально возвращается не он, а uint8_t. По крайней мере, в linux:


echo $'main() {return (-96);}' | tcc -run - ; echo $?

покажет 160. Поэтому ещё возникает вопрос к смыслу задания: что имеется ввиду под «значением возврата»? Стандарт (C99) по этому поводу говорит что, во‐первых, возврат из main() эквивалентен вызову exit(); во‐вторых, что вызов exit() с нулём или EXIT_SUCCESS вызывает определённую реализацией (implementation-defined) форму завершения со статусом «успех», вызов с EXIT_FAILURE — определённую реализацией форму завершения со статусом «провал», а вызов с любым другим аргументом вызывает завершение со статусом, определяемым реализацией. Т.е. если под «возвращаемым значением» имеется ввиду что‐либо отличное от «значения аргумента return», то ответ на все вопросы, где это значение аргумент не 0, EXIT_SUCCESS или EXIT_FAILURE (последние два — только и исключительно в виде макросов либо каких‐либо выражений, получающих значение этих макросов, а не конкретных значений) — «я не знаю».

Третий вопрос полностью о темных углах. Начиная с того, что ни переполнения integer


Переполнение возникнет лишь если результат умножения не помещается в int.

Преобразование между целочисленными типами данных (в данном случае от int к char) — немного другая статья. Если результирующий тип — беззнаковый, то гарантируется модулярная арифметика по модулю 2^<число бит>; в противном случае результат будет implementation-defined (в поздних стандартах на этот случай появилась фраза "...or an implementation-defined signal is raised", но это всё равно не undefined behavior).

Более того, размер типа char в битах не определен. Существовали платформы где он был по 6 бит (помните триграфы?) и существуют платформы где все пять целочисленных типов по 32 бита.


Стандарт требует, чтобы CHAR_BIT был не меньше 8 бит.
В первую очередь стоит заметить, что язык С уже давно не поддерживает объявлений функций без явного указания типа возвращаемого значения. Правило неявного int было отменено ещев 1999 году. Поэтому использованное в примерах 'main ()' без явного 'int' — это уже не С.
Напоминаю, что мы говорим про язык С.

Во-первых, язык С для компилятора GCC, который скрывается за ideone, начинается с флагов `-std=...` и `-pedantic-errors`. Без этих флагов компилятор GCC является компилятором развеселого студенческого языка для пивных вечеринок, никакого отношения к С не имеющего.

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

Научитесь пользоваться хотя бы coliru

http://coliru.stacked-crooked.com/a/aef58ce98b518d02

Существуют и другие уважаемые онлайновые фронтэнды. Но не ideone.
> Научитесь пользоваться хотя бы coliru
Этот ядовитый комментарий был излишним, с остальным вынужден согласиться.
Новее -std=c90 модификация языка это уже избыточные финтифлюшки, а в C90 еще можно не объявлять тип.

Вот С++ изначально ответственнее относится к контролю типов.

Во-первых, ни в коем случае. Язык С — это язык не ниже С99. Формально С11 — нынешний стандарт, но его можно назвать достаточно минорной модификацией С99 (ака "финтифлюшками"). Но С99 обсуждению не подлежит.


Во-вторых, как уже говорил выше, контроль типов всегда был строго идентичным в С и С++, за исключением возможности неявной конверсии из void * в С. Да, современный С++ усилил контроль за конверсиями (за счет запрета narrowing conversions в ряде контекстов), но это ведь не то, что вы имели в виду, правда?

"Существовали платформы где он был по 6 бит (помните триграфы?)..." — это замечание, конечно, совершенно "мимо кассы". Особенности символьного набора (execution character set) конкретной платформы формально влияют на размет типа 'char' только тем, что обязаны в него помещаться. Не более того.


Даже в стариннейшем "C Reference Manual" тип 'char' уже постулировался, как знаковый 8-битный тип в формате 2's-complement. А дальше его спецификация становилась лишь еще более абстрактной и свободной.

C и C++ действительно имеют довольно много элементов, порождающих межплатформенную несовместимость, наличие которых не оправдано с точки зрения оптимизации. Если платформозависимый размер int еще оправдан, когда заведомо достаточно 8 бит, то short и long только привносят несовместимость. Выход из этой ситуации на мой взгляд — не переход на новые языки, а эволюция C++. Нужно добавлять новые конструкции, имеющие более четко определенное поведение на всех платфотмах, а старые объявлять не рекомендованными к применению, оставленными только для совместимости. Например, вместо int, short, long и long long, (а во многих случаях и char), ввести int, где n-гарантированное минимальное число бит в платформозависимом типе — поведение станет детерминированным, а поле для оптимизации только увеличится. Вместо int8_t, int16_t и т.д. — ввести похожую конструкцию, но с чётко фиксированным размером в битах. Способ упаковки структур и классов лучше рекомендовать явно указывать в определении — например struct<pack,1> — упакованная по границе байта, struct<pack,1b> — упакованная по границе бита, struct — не упакованная, но с сохранением порядка полей, struct — компилятор может изменять порядок полей, например заполняя пробелы, оставленные при выравнивании. Объединив первое и второе получим новый, более прозрачный способ объявления битовых полей в структуре — битовые поля не привязанные к границам байтов, что может быть полезно при передаче данных, и при этом не так уж затратно.
Что касается использования C вместо С++ — если для платформы доступен компилятор C++ — я не вижу в этом смысла, лучше выделить некоторые подмножества С++, такие как например, Embedded C++, или более близкие к С, но позволяющие уйти от #define, а в случае необходимости — перейти к более расширенному подмножеству.
Проблемы с форматированием:
— вместо int, short, long и long long ввести int<«N»>где «N»-гарантированное минимальное число бит в платформозависимом типе;
— не упакованная структура с сохранением порядка полей:
struct<ordered>

— структура, в которой компилятор может изменять порядок полей:
struct<auto>
вместо int, short, long и long long ввести int<«N»>где «N»-гарантированное минимальное число бит в платформозависимом типе;


Во-первых, гарантии там и так есть (исходя из <limits.h>):
  • char, signed char, unsigned char — не меньше 8 бит;
  • short, unsigned short, int, unsigned int — не меньше 16 бит;
  • long, unsigned long — не меньше 32 бит;
  • long long, unsigned long long (since C99) — не меньше 64 бит.


Во-вторых, начиная с C99 есть <stdint.h> с его [u]int_least&ltN&gt_t и [u]int_fast&ltN&gt_t (соответственно «самые маленькие» и «самые быстрые» типы с минимальной заданной битностью).

Вместо int8_t, int16_t и т.д. — ввести похожую конструкцию, но с чётко фиксированным размером в битах.


У них-то как раз фиксированные размеры — ровно столько значащих бит, сколько указано. Более того, для них гарантируется также:
  • отсутствие битов-заполнителей;
  • кодирование при помощи двоичного дополнительного кода (в то время как для остальных знаковых типов допускается любая pure binary system — как минимум прямой код и обратный код, а не только двоичный дополнительный).

Нефиксированным остаётся лишь порядок байтов.

Если платформозависимый размер int еще оправдан


На практике он, как ни странно, оказался относительно платформонезависимым — 32 бита что на 32-битных, что на 64-битных платформах:) В отличие от long, который (в беззнаковом варианте), например, в ядре Linux используется чуть ли не как синоним void*.
>гарантии там и так есть
Это, конечно, хорошо, но половинчато и не универсально. В некоторых процессорах (DSP) промежуточные результаты вычислений хранятся в регистрах размером 18, 20, 24 или 48 бит. В компиляторах для них есть соответствующие типы, но с переносимостью кода есть проблемы. Для 8-битных микроконтроллеров были бы полезны типы с разрядностью 24 и 40 бит. А кое-где уже поддерживается 128-разрядная архитектура. Вместо того чтобы каждый раз менять стандарт, лучше 1 раз ввести универсальную конструкцию, её поддержка компиляторами потребует не слишком много усилий.
>>Вместо int8_t, int16_t и т.д. — ввести похожую конструкцию, но с чётко фиксированным размером в битах
>У них-то как раз фиксированные размеры
Я имел в виду конструкцию, похожую на предложенную мной выше int<«N»>, с произвольной фиксированной разрядностью. Например int<16f>, int<18f>. А за int_fast_t спасибо, не знал, буду использовать.
Only those users with full accounts are able to leave comments. Log in, please.