Comments 270
На ум приходят Ada и Rust, но первый язык так и набрал популярности (хотя он во многих аспектах намного лучше подходит для системного программирования), а второй пока еще довольно молод, но я искренне за него болею.
Можно, конечно, попробовать «причесать» уже имеющийся С (Cyclone, Checked C, etc.) или ограничиться небольшим «безопасным» подмножеством языка (JPL Coding Rules, MISRA C, etc.), но это все мало кто может себе позволить, т.к. программировать с такими ограничениями умеют меньшее количество специалистов, и сама разработка в итоге становится дороже (иногда существенно) и медленее (иногда на порядок).
Я, наверное, ни за какие деньги не взялся бы писать на чистом С. Это хороший язык, но слишком простой. Я не доверяю софту, написанному на С, потому что понимаю, как сложно писать на этом языке большие системы без ошибок. Это тот же ассемблер, только кросс-платформенный (ну, по большей части).
В ядре linux, на сколько знаю, используют «С с классами»
Но как, сэр?
Это же чисто эппловская примочка, да ещё и какая-то высокоуровневая…
Ядро Linux объектно-ориентировано, хоть и написано на C. Такие дела.
Соотв., Ваше утверждение выглядит странно.
Я уже не говорю о том, что «структура, хранящая указатели на функции» — не обязательно эмулирует классы.
Я уже не говорю о том, что «структура, хранящая указатели на функции» — не обязательно эмулирует классы.
Не обязательно. А ещё для реализации ООП не обязательно иметь ключевое слово class в языке. Более того, язык вообще не может знать про классы. И при этом на нём можно писать объектно-ориентированный код.
Если у нас есть конкретное очерченная задача (git) — хватит и C. Если нет rich runtime (linux) — опять С. Если мы максимально экономим ресурсы (nginx) — снова С.
Но при этом почти весь хороший код на C всё равно оперирует объектами. Да, там часто проблемы с полиморфизмом и наследованием. Но абстракция данных и инкапсуляция цветет пышным цветом. Достаточно посмотреть API любой сишной библиотеки (кроме libc, естественно).
Я не противник С++, но в ветке видел несколько ответов почему в некоторых задачах не взять готовый С++ вместо С:
- Больший рантайм. Напирмер в андройде libstdc++ есть несколько на выбор Android/Sdk/ndk-bundle/sources/cxx-stl/, подключив любой и хоть немного заюзав — мой apk вырастет. Конечно можно и на урезанном С++ писать, но тут писали якобы таких спецов меньше. Так же если мы скажем что-то оптимизируем и глазами изучаем скажем .map файл, куча мелких с++ функций рантайма начнут мешать.
- Маленький компилятор, JIT tcc умещается где-то в 100-200кб. Аналогов тут не так много, разве что lua. Так что если мы хотим вставить в тот же apk и не раздуть его, llvm тут не подойдет.
Может в этом и есть какой-то смысл, но не в ущерб читаемости, удобству и качеству. По крайней мере на тех платформах, где оперируют уже давно гигабайтами.
В ближайший месяц мы выпускаем приложение на 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++ нужен гораздо более жирный рантайм, а специалистов, умеющих писать на «обрезаном» С++ — намного меньше, чем специалистов по С, т.к. такому «ограниченному» использованию С++ почти нигде не обучают.
Ну, типа — там несколько связанных битов, конфигурирующих ввод-вывод и сам I/O pin.
Разумеется, это было несколько инлайновых шаблонов с наследованием — выглядело, в общем, неплохо.
А биты адресовать невозможно (т.е.Си с ними вообще с ума сходил).
Ну и конструкторы-деструкторы — штука няшная.
А биты адресовать невозможно (т.е.Си с ними вообще с ума сходил).
Битовые поля структур не могли бы помочь?
А биты адресовать невозможно
std::vector<bool> смотрит на это заявление с недоумением.
Внутри.
А.
Значиццо у нас есть микроконтроллер, для которого есть Си/С++ со специфическим расширением — типом _bit.
Некоторые биты имеют специфическую функциональность — ну там ввод/вывод, настрока пина на вывод, открытый сток/пуш-пул…
И поверх всей этой радости предлагается присобачивать std::vector, который вообще «Не обязательно хранит свои данные в одном непрерывном куске памяти.» (© cppreference)?
Обёртку я тоже нопейсать могу — что я, собственно, там и сделал.
#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;
}
Учитывая то, что исключений здесь быть не может, в чём подвох?
vector<bool>::reference
и всё отлично работает.Нередко более эффективный код на шаблонах, чем код в лоб, правда ценой раздутия кода. То же самое, конечно, можно сделать на сях, но дольше и с большей вероятностью ошибки.
Язык С практически ничем не отличается от языка С++ в области строгости типизации. Единственное отличие, которое навскидку приходит в голову — это возможность неявной конверсии void *
в другие объектные типы указателей в языке С. Все.
Так о какой строгой типизации вы ведете речь?
Явное приведение типов (которое в 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 ему действительно ничем не помогут. Но контроль типов — это немного про другое.
Это всего лишь дружеская необязательная помощь компилятора.
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». А успешная компиляция программы допускается даже если последняя некорректна.
Нет. Это популярное наивное заблуждение.
Во-первых, это было бы справедливо только в отношении компиляторов, которые строго и аккуратно разделяют 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. К чему здесь вдруг ваши разглагольствования на тему "что вы предложите мне выкинуть" — мне не ясно. Никто вам тут ничего выкидывать пока не предлагал.
Не буду уточнять версии, т.к.больше чем уверен что этот код соберется любым из десятка имеющихся у меня С-компиляторов.
Это глупая дискуссия между теоретиком и практиком.
О! Уж совсем детские наезды пошли!
Никакого спора тут нет и быть не может. Все эти вопросы уже давным-давно имеют четкие и однозначные ответы. Вы просто по неопытности своей в первый раз эти ответы слышите. Поэтому я даю их вам под запись, а вы — тщательно изучаете и зазубриваете наизусть. Никакого спора.
Что касается вашей феерически "альтернативной" интерпретации работы этих компиляторов — здесь это никому не интересно. Здесь речь идет именно и только о языке С. И все эти компиляторы немедленно откажутся трактовать ваш ошибочный код, как код на С. В противном случае они бы под общий хохот были бы вышвырнуты с рынка смачным пинком под зад.
Что же касается вашей "практики" — это не практика, заблудший вы наш, это что-то вроде "диссертации Василия Ивановича", в которой он, согласно известному анекдоту, оторвав таракану ноги, пришел к выводу, что "безногий таракан не слышит". Или, если угодно, это — изучение творчества группы Битлз по напевам Рабиновича, согласно другому известному анекдоту.
Если вы намерены серьезно изучать и использовать язык С, всю эту вашу "практику" вам придется тщательно стереть из головы — она бесполезна и будет вам только мешать. Придется накапливать с нуля совсем новую практику. А уж когда вы напрактикуетесь хотя бы с половину моего — вот тогда и понимание теории подтянется. Я знаю, ибо сам когда-то таким был.
Потому оставьте весь поучающий пафос себе.
Молодой человек, вы со своими "ссылками не первоисточники" смачно сели в лужу именно так, как садится в нее практически каждый новичок, неправильно понявший 6.3.2.3. Вы у меня уже, наверное, тысячный...
Учитесь не просто читать, а еще и понимать прочитанное. Я вам там все подробно разъяснил, под вашей "ссылкой не первоисточник".
Поэтому еще раз, у вас появляется редчайшая и уникальнейшая возможность поговорить с "отцами", думайте не о каком-то воображаемом "пафосе", а том, чтобы записывать и тщательнейше зубрить каждое услышанное слово. Другой такой возможности вам может не предоставиться.
Например вижу ошибку выхода за границу массива
То есть вы ещё не перешли на utf-8?
Чтобы (достоверно) увидеть ошибку выхода за границы массива в вашем примере, надо знать компилятор и кодировку исходника. Без этой информации отсутствие выхода за границы более вероятно.
В цивилизованном мире UTF-8 — кодировка по умолчанию.У меня — нет, на гитхабе я тоже встречал в основном ascii.
Полагаться на кодировку исходников — чистой воды непереносимость.
ASCII является валидным UTF-8, если что.
Он может быть в ASCII-совместимой кодировке — например, в UTF-8 (весьма вероятно, если вы под Linux-ом) или Windows-1251 (если вы под виндой).
Поведение там определено языком С, хотя и непереносимо (только для второго ворнинга).
Кстати именно буквоедство свойственно многим новичкам — это к посту ниже по некоторых индивидуумов.
Поведение там определено языком С, хотя и непереносимо (только для второго ворнинга).
Нет.
Программа некорректна — содержит constraint violations, т.е. синтаксические или семантические ошибки. Такая программа именно некомпилируема (!) как программа на языке С.
Компиляторам разрешается компилировать такой код с той оговоркой, что поведение такой программы языком С не определено, ибо программой на С такой код не является.
И для этого достаточно только main()
, не говоря уже о int *p = 123;
. Дальше main()
уже можно не смотреть.
И не надо пытаться вертеть хвостом с вашим выходом за пределы массива. Он тут не имеет никакого значения. Кстати, выход за пределы массива не является constraint violation, т.е. компиляторы имеют полное право компилировать ваш выход за пределы даже и не пискнув. Диагностики времени компиляции тут не требуется.
Это из 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;
. Язык безусловно требует явного каста для выполнения такого преобразования.
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 — мне до сих пор не ясно.
Надо заметить, что Брайан Керниган не имеет никакого отношения к разработке языка С вообще, как он сам не раз заявлял. Как активный участник разработки языка В (предшественника С) он участвовал в подготовке книги, но не более того.
http://www.math.utah.edu/computing/compilers/c/Ritchie-CReferenceManual.pdf
В этом языке действительно чувствуется сильное влияние ассемблера. Однако все это было выкинуто из языка очень быстро: CRM C очень сильно отличается от K&R C, не говоря уже о ANSI C, не говоря уже о современном С.
То есть вы сосредоточены не только на решении задачи, вам нужно еще и с компилятором побороться.
С++ не так просто скомпилировать на разных версиях операционной системы даже при использовании одного и того же компилятора (не говоря уже про разные компиляторы и разные платформы и разные стандарты С++).
Например,
https://github.com/cocaine/cocaine-core
Это одна из базовых технологий Яндекса, сделанная не самыми глупыми программистами.
Но это требует серьезных ДОПОЛНИТЕЛЬНЫХ затрат или просто СПЕЦИАЛЬНЫХ знаний.
Я утверждаю, что львинная часть Сипласпласовцев таковыми не очень то и стремится овладеть сосредоточившись на своей узкой задаче.
Ну не должен язык, претендующий на базовый, быть таким ненадежно-непредсказуемым.
Чтобы было понятно о чем речь и что проблема решаема в принципе — сравните, сколь небольшими усилиями делается переносимая программа на Python или Go.
С С++ не сравнить — это просто небо и земля.
В Python и Go если разработчику и нужно с чем то бороться при этом — то с отдельными аспектами не POSIX API (Windows). Что как раз ожидаемо и логично.
С С++ дополнительно приходится бороться с различиями в компиляторах и библиотеках — даже в пределах ОДНОЙ платформы.
P.S.:
Яндекс приведен как контора, которая может позволить себе разработчиков уровня выше среднего. И если даже они косячат со своими базовыми вещами (эта штука, что я привел — занимается запуском и мониторингом контейнеров с другим ПО в кластере Яндекса) — то что же ожидать от основной массы?
Не должен быть язык таким. Это плохое свойство. Существенный недостаток. Никак он не может претендовать на системный, базовый.
Но я уделяю внимание использованию стандартного С++ и всячески избегаю любых платформозависимых расширений. "-pedantic-errors", /W4, периодический статический анализ и всё такое.
Да, там кое-где код написано не универсально. Но дело не только в этом.
Дело не только в СТАНДАРТНОМ С++. Там еще куча ЗАВИСИМОСТЕЙ.
Все от вас зависит только в ПРОСТЕЙШЕМ ПО, которое вы пишете сами.
Как только начинаете использовать внешние вещи (библиотеки) — количество сюрпризов увеличивается.
Вплоть до багов в такой казалось бы базово-универсальной вещи как Boost, не говоря уже о более специфичных библиотеках…
Буст не использую, он слишком большой и сложный в интеграции.
Средний программист С++ (коих подавляющее большинство) понятия не имеет как писать 100% переносимый код.
Вы же приводите в пример просто «историю успеха».
Никто не спорит, что при желании на С++ можно писать переносимо.
Совсем другой вопрос — а какими это достигается усилиями, по сравнению с более естественными в этом отношении системами программирования. Ну например, Python и Go и Java…
Как только начинаешь писать под больше, чем один компилятор и/или одну платформу — очень быстро дисциплинируешься и всё встаёт на свои места.
А ваши слова про подавляющее большинство — пустой звук, не подкреплённый никакими доказательствами.
На Go и Питоне уже можно писать GUI, или по-прежнему как всегда? Питон вообще не рассматриваю как что-то большее, чем средство написания скриптов для автоматизации каких-то относительно небольших процессов. Java в целом хороша, но жирный рантайм, кривой UI и производительность «как повезёт». Это моё ИМХО, your mileage may vary.
Ваш частный случай — это всего лишь ваш частный случай.
Факты таковы, что большая часть людей даже ни разу не стремятся профессионально рости. Это касается любой профессии, а не только профессии программиста.
И, к сожалению, создатели третьих библиотек не всегда уважают стандарт и не всегда проверяют кросс-платформенность.
Я уже привел пример Яндекса, который может себе позволить нанять специалиста заметно выше среднего (и дело не только в деньгах, они могут позволить себе нанять специалиста даже потратив меньше, потому как все прекрасно понимают, что после Яндекса — все пути тебе открыты).
И тем не менее — видим огромные косяки. Там даже не так просто скомпилировать под Ubuntu другой версии…
А ведь это не какой то локальный продукт — их Cocaine базис облака Яндекс и распространяется свободно для любого желающего.
Для того, чтобы убедиться, что большинство С++ создают продукты такого же качества — достаточно побольше взять открытых проектов и попытаться их скомпилировать (даже не запустить!!! ) на различных системах.
Если я скажу что я делал это — и все очень часто плохо — это же не будет для вас доказательством? Вы можете это понять только самостоятельно.
Проблема с GUI решаема.
Совсем не обязательно создавать монолит. GUI (если у вас не нечто требовательное до 3D, к примеру) — вполне безболезненно может существовать отдельно от вычислительной части.
И, очень интересно, а что вы называете кросс-платформенностью своей системы? Где именно возможна ее работа, на каких ОС?
2. Вы аппелируете к авторитету Яндекса, но для меня это не авторитет.
3. В репозитории есть README. Там принято писать список поддерживаемых платформ и компиляторов. Ожидать беспроблемной сборки на других системах не стоит. Есть спецификация, там написано, что система гарантирует. Всё остальное вам никто не обещал, так чего вы ожидаете? Вы, наверное, из той категории людей, которые ставят приложению в Google Play негативные отзывы за то, что приложение не умеет ещё и кофе варить.
О чём мы вообще спорим? На С++ можно писать непортируемый код? Да, можно. И на Java можно. Банально, вызвали WinAPI функцию — всё, ваш код стал непортируемым. На С++ вызывать сторонние компоненты проще, и для меня это преимущество, а не недостаток. Правильное использование фич всегда на совести програмиста, а не языка.
Зато писать портируемый код на современном С++ намного проще, чем на С (кроме самых embedded платформ, наверное).
4. GUI на основе другой технологии — лишняя головная боль. Всякие прослойки-адаптеры., через которые надо правильно протащить каждый чих.
5. Я уже говорил, что почти все мои программы работают на Linux, Windows и OS X, а некоторые из них также на Android и iOS.
Лично вы работали в окружении опытных спецов — вам очень повезло.
Нет, не так. ВАМ ОЧЕНЬ СИЛЬНО ПОВЕЗЛО, ЕСЛИ ВОКРУГ ВАС ОДНИ ПРОФИ.
В реальной жизни это большая и большая редкость.
В среднем человек любой квалификации довольно посредственнен как профессионал.
То, что вы еще не сталкивались с огромным числом не профи — говорит о вашем малом жизненном опыте.
И только.
Тем самым вы и подтвердили мое утверждение.
> но жизнь, не без моей помощи, наставила их на путь истинный.
Разумеется, кто же будет делать лишнюю работу в здравом уме и твердой памяти?
С C++ этой работы слишком много, вот в чем дело.
Какое? Что можно написать непортируемый код? Так я ещё два комментария назад сказал, что это так, но это не аргумент.
>C C++ этой работы слишком много, вот в чем дело.
Я так не считаю. Единственная серьёзная сложность была — избавиться от анонимных union.
В вашем собственном коде.
А как же про сторонние библиотеки? Повезло?
Во многих других языках данная проблема просто и не может возникнуть. В других языках ты как правило борешься всего то с особенностями программной платформы (например Windows vs Unix).
В случае C++ — плюс еще и с самим языком.
Это не дело.
Не должно от «анонимных union» ничего зависеть. Это бага дизайна языка
Библиотеки изначально брались только кросс-платформенные. Никакого везения, холодный расчёт.
С другими языками таких разночтений — огромная редкость.
Как можно на столь зыбком фундаменте жить.
Это не совершенно непроизводственные затраты.
Вместо того, чтобы сосредоточиться на задаче — ты тратить часть своего времени на борьбу со своим инструментом.
Правда там ответ на эти вопросы будет таким же.
Почему нет? Законы эволюции вполне действуют и на языки программирования. Паскаль почти вымер. Кобол почти вымер. А Си — слишком хорош, чтобы его выкинуть.
А не слишком хорош.
Как минимум без Си исчезают все распространенные операционные системы, приличная доля компиляторов/интерпретаторов, туева хуча утилит и базовых библиотек без которых не сможет жить или создаваться софт более высокого уровня и пр. и пр.
Но когда речь идет о прикладном ПО, то планка совсем иная.
UB в Си побольше, чем в «любом языке», используемом для написания прикладного ПО, если в качестве любого рассматривать Python, Java, Go.
Если под «любым языком» вы понимаете C++ — тогда конечно, да, я с вами соглашусь.
Плюсы для меня темный перегруженный лес, так сложилось исторически, поэтому сравнивать с ними не хочу и не берусь)
Ну я бы не сказал, что в Python, Ruby, Perl намного меньше undefined behaviors. Все они основаны на Си-рантайме и также в основном исповедуют типа без привязки к разрядности архитектуры. Хотя есть и типы более жестко определенные, но тем не менее. И это только один из аспектов.
Например, большая часть языков основана сейчас на LLVM, GCC или JVM, и вынуждены следовать их UB.
В языке их нет. Это просто определения в библиотеке, описываемые под известный компилятор и известные платформы.
Если компилятор, например, не определит макросы, по которым stdlib догадается о размерности базовых типов, то "всё пропало". А он, по стандарту, и не обязан это делать.
uint_least16_t имеет совсем иное (не столь строго определённое) поведение. в частности, при битовых сдвигах.
- 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).
И нет, я не предлагаю перейти всем на 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.
Но параметризация должна быть явная, как и модульность. Вообще все должно быть явное, стандартизированное и общее для всех компиляторов.
Уже не решится проблема. Очевидно.
Компилятор, зависимый от платформы, который не должен пропускать подозрительные вещи.
В Си же можно накосячить «втихую».
Во-вторых, а что считать подозрительным? Подозрительно что-то может быть с точки зрения платформы, конкретного компилятора, но мы же определяем независимый от предрассудков язык…
Да даже порядок вычисления параметров метода, его неопределённость подозрительна, только если мы вычисляем их один за другим. А если все сразу параллельно, считая глобальные переменные разделяемыми между контекстами, а локальные — в зависимости от погоды на Марсе?
Конечно, случайно взятая библиотека в таких условиях не соберётся сразу без проблем. Но модификация библиотеки по отметкам компилятора/анализатора/отладчика «здесь может быть гонка, здесь буду синхронизировать» — куда проще, чем писать тот же SQLite с нуля только потому, что наше подмножество языка ну очень уж далеко уехало от их подмножества.
Сейчас же его применяют и для прикладного ПО, где спускаться на столь низкий уровень нет необходимости (тем более сегодняшние компиляторы очень хорошо оптимизируют).
Но Си все так же остается всё таким же «ассемблерно-близким», хотя для большей части продуктов, создаваемых с его помощью — таких вольностей не требуется, они только увеличивают риски косяков…
Зачем мы пишем прикладное ПО на «почти ассемблере» до сих пор?
Или вы из «новой волны», которая пишет под Arduino на JavaScript?
Как видите — совершенно ничего сложного; раз-два, и готово.
Создатели и последующие развиватели C пошли немного совсем другим путём: они знали, что есть процессоры разной архитектуры, иногда — совершенно дикой с т.з. людей, привыкших к i*86 и ARM. Поэтому в C было определено поведение программ в определённых случаях. И было чётко сказано, что за пределами этой области поведение программы м.б. всяким-разным.
Например, случай:
x = 1 << y;
правильно работает при y>=0. А при отрицательных y поведение не определено. Если Вы хотите, чтобы для отрицательных y выполнялось
x = 1 >> (-y);
то никто не мешает Вам написать проверку y на знак и выбрать соответствующее действие.
Более того: прямо на этапе компиляции можно проверить, как данная архитектура (процессор и компилятор) ведёт себя для данного случая. И можно отказаться компилироваться на «плохой» архитектуре. Или для «плохой» архитектуры можно сделать менее эффективный, зато точно работающий код.
4
А. 0
В. 1
С. 16
D. Я не знаю
1) А, В и С возможны (в зависимости от конкретных размеров типов).
2) A или В (потому что == возвращает или 0 или 1). Но не С (потому что == не может вернуть 2, стандарт это явно оговаривает)
3) А, B или С возможны, в зависимости от размера char и от его знаковости. Но если копнуть глубже, то тут вариант «Не знаю» действительно подходит лучше, потому что стандарт С не оговаривает кодировку символов! И код пробела, вообще-то, может быть любым (стандарт только обязывает его влезать в 5 бит).
4) Вопроса нет, поэтому я действительно не знаю.
5) Это не «я не знаю», это называется «неопределенное поведение».
Еще пара моментов:
— Почему-то паддинг в структурах вы называете «отступы» — никогда не слышал такого применения, обычно отступ — это отступ в начале строки исходного кода. Но допустим, с русскоязычной терминологией у меня плохо.
— «Более того, размер типа char в битах не определен.» Вообще-то, он определен в макросе CHAR_BITS. Вы, видимо, имели в виду, что он не определен в стандарте — это так.
на практике мы работаем на определенных платформах и компиляторах, и их поведение нам известно.
итого. верные ответы должны быть такими:
1)зависит от окружения и настроек выравнивания.
2)зависит от окружения.
3,4)опять же зависит от окружения, либо UB.
5)UB.
в общем-то таких примеров можно массу подобрать
получился опрос для школьников… с понурыми ответами у доски вида «нууу я не знаю...»
надеялся на что-то более серьезное, а не про сферических коней в вакууме.
Великая месса в С миноре Вольфганга Амадеуса Моцарта. Да, Моцарт тоже писал на С.
Вас обманули, причем дважды. Во-первых, по-русски это называется до-минор. Надеюсь, на языке "До" Моцарт не писал. Во-вторых, в латинизированной нотации до-минор — это c (прописная буква), а C (заглавная буква) — это до-мажор.
Бонус: похожий на букву C знак в начале каждого нотного стана (после ключевых знаков) — это разорванная окружность, обозначающая размер 4/4 (и происходящая из мензуральной нотации, где она означала несовершенный, т.е. четно-дольный метр).
(Ну и еще по-русски это называется "Большая месса")
Это не трюки, а фундаментальные свойства С, как платформы. Это атомы, из которых состоит С. Абсолютно любая программа на языке С опирается на эти «трюки».
Во-первых, это оголтелое вранье. Никакого неопределенного поведения в первом и втором примерах нет. Третий и четвертый смогут содержать его потенциально, но речь там идет не о том. Явному неопределенному поведению посвящен только пятый пример.
Во-вторых, темой этой дискуссии является избежание неопределенного поведения, а не некое "опирание" на него. Я по-моему ясно это постулировал в комментариях выше. Внимательнее читаем комментарии.
Если использовать только то, что описывает стандарт без всяких(пусть и общеупотребительных) трюков, то вероятность что-то сломать гораздо меньше, а если и сломается — всегда можно попенять разработчикам компилятора, что они неправильно реализовывают стандарт.
Рассмотрим два пути:
1. правый операнд при сложении вычисляется первым
Тогда наше выражение пройдёт следующий путь
> i = 0;
> 0++ + ++0 -> 1++ + 1 -> 2 (i = 2)
2. левый операнд вычисляется первым
> i = 0;
> 0++ + ++0 -> 0 + ++1 -> 2 (i = 2)
и результат равен 2, и значение i равно 2
Как получить три, не придумал, но в более сложных и запутанных ситуациях может получится и больше разных ответов. Причем они все не будут багами, потому что UB.
В следующей программе
#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 по Стандарту» и баг закрывают.
Также в стандарте написано, что если у вас есть два 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.
Логика такая: компилятор принял i за константу внутри точки следования и оптимизировал левый инкремент до возврата нуля, а сам инкремент поставил в конец точки следования (и отбросил за ненадобностью, ведь дальше тела функции i не используется). После чего выполнил правый инкремент, сложение и вернул результат.
Существуют такие расширения как GCC атрибуты aligned и packed которые позволяют вам получить некоторый контроль над этим процессом, но они не стандартизированные.
Чё это они не стандартизированы? Уже лет 5 как.
C11 ISO Standard, 6.7.5 Alignment specifier.
April 2011.
Хотя можно было бы и разработать несколько подмножеств стандарта и, соответственно, режимов компиляции. Например «легкий» — то что сейчас и «строгий» — рантайм из-за проверок утяжеляется, но UB минимизируются
В целом для своего времени не привязывать типы к размерам, возможно, было неплохой идеей. Ибо бывали платформы и 16-битные, и 32-битные, и у каждого типа процессора были свои оптимальные размеры для целочисленных инструкций, которые разработчик процессора мог рекомендовать автору компилятора — это делало программы быстрее, причём, с теми рабочими частотами, ощутимо быстрее.
Что до UB по любому странному поводу — дык, не с проста. Каждое описанное UB это зарытый где-то рядом повод для оптимизации. В условиях нехватки процессорного времени тоже отличное решение.
Беда только в том, что теперь процессорного времени на человека есть целая гора, это позволяет нам запускать тяжеленные софтины на прожорливой Java, обрабатывать запросы к веб-серверам на интерпретируемых языках и делать много других странных вещей, недопустимых во времена создания C. Возможно, стоит сместить фокус системного программирования с оптимальности на безопасность?
Я уже давно привык юзать конкретные чёткие типы, как например uint32_t, int8_t и так далее.
Для математики тоже есть свои типы с чёткими размерами, от float64_t до float32_t, мелочь не используется -потому как считается в тиках намного дольше.
Насчёт выравнивания структур при оптимизации. Самый простой способ — выполнить объединение двух заведомо соразмерных структур. Для запрета перемешивания — объединение с перехлёстом.
Подобные знания требуются программисту который пишет на уровне железа — это конкретное обращение к регистрам в памяти. Всем остальным — лишний багаж знаний.
Это хорошо что ещё не затронули тему передачи параметров в функцию. Для разных архитектур ядра — параметры могут передаваться самым удивительным способом. С лева на право, с право налево, через регистры ядра, через теневые регистры ядра (не знали? :) ), через стек, через внешнюю память.
Зоопарк.
Архитектура ядра оказывает влияние на calling convention, но это не единственный определяющий фактор. ОС (если есть) или компилятор (если нет) могут из каких‐то соображений выбрать не самое оптимальное соглашение, которому будут следовать. (В принципе, компилятор может даже проигнорировать соглашение о способе вызова, принятое в определённой ОС, но по понятным причинам (взаимодействие с чужими библиотеками), скорее всего, не будет этого делать.)
Хотя я и ошибся с promote to int во втором примере )
Все такие исключительные ситуации для своей платформы и компилятора и его версии не запомнить (и не узнать) коммент в этом топике — см цитату.
Я раньше пользовался… К примеру переполнением uint8_t для счетчика циклического буфера чтобы избавится от всяких if, но чем больше узнаю, тем больше удивляюсь.jpg и стараюсь так не делать.
Но опять же, есть определенный перечень UB, на которые ты знаешь, как будет у тебя, но все равно сознательно пишешь нормально.
Я раньше пользовался… К примеру переполнением uint8_t для счетчика циклического буфера чтобы избавится от всяких if
А почему перестали? У uint8_t никаких проблем быть не должно.
Andrey2008 из PVS-Studio утверждает с переполнением лучше не шутить
Обычно глупости выглядят как-то так (собирательный образ):(Хотя в статье проблема в неправильном типе, и переполнение работает так как ожидается. И речь в статье не про UB при переполнении, но думаю у него опыта и знаний больше чем у меня.)
Ну да, формально переполнение 'int' приводит к неопределенному повреждению. Но это не более чем болтовня. На практике, всегда можно сказать что получится.
Можете считать это религией или стилем. Но мне кажется он хорош. Если нет острой необходимости, то не использую хаки (и 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 (если таковой тип предоставляется реализацией) четко оговорены спецификацией языка и никакой неоднозначности не допускают. "Зацикленное" (модульное) поведение беззнаковых типов — очень полезная фича языка, пользоваться которой можно и нужно. Поэтому ваше "чем больше узнаю", похоже, привело лишь к тому, что с водой вы выплеснули и ребенка.
Ну в любом случае — если не знаешь можно перестраховаться, а знаешь четкую границу — можно и делать финт ушами и быть уверенным в безопасности. Спасибо.
Когда смотрел предлагаемые ответы, среди них не было правильного. Потому что ответ «Я не знаю» в данном случае не отражает реалий.
Поэтому это не тест, а подвох из серии «На грядке сидело 3 воробья, одного поймала кошка, сколько воробьёв осталось?».
То, что в С куча тёмных, платформозависимых и компиляторозависимых моментов, совершенно не удивляет, а удивляет, что автор за 15 лет об этом не догадывался…
Но даже так, мы не сравниваем типы, мы сравниваем размеры. И единственная гарантия которую стандарт дает о размерах short int и int в том, что предшествующий не должен быть больше последующего.
В добавок к этому: оператор sizeof возвращает не размер операнда, а его пропорциональное (или как это правильно сказать) отношение к размеру char. То есть sizeof(int) == 4 еще не означает что int равен четырем байтам. Это означает только что в int может вместиться четыре char'а (Wikipedia).
То есть 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.
С другой стороны, стандарт Си описывает всё в терминах абстрактной машины, не привязываясь ни к каким свойствам реальных машин. В Си своя модель памяти, которая не обязана сколько-нибудь явно соответствовать организации памяти той реальной среды, в которой программа будет выполняться (собственно, и не соответствует: компиляторы одни переменные помещают в регистры, другие полностью выкидывают, переупорядочивают операции чтения/записи и т. д.). Поэтому и понятие «байт» в стандарте Си, строго говоря, не обязано совпадать с понятием байта в «железе».
Так сколько то в день попыток бесплатных, хватит чтобы оценить уровень пола.
Хорошо, если этот инструмент будет удобным в работе. Тогда и результат будет быстрее, и, возможно, качественнее.
Как правило, современным инструментом работать намного эффективнее. Вместо лучковой пилы лучше взять электролобзик. И не нужно думать, как раньше, над углом заточки зубцов пилы, затачивать их вручную. Сейчас просто идешь в магазин, покупаешь пилку «по дереву», вставляешь в лобзик, и пилишь со скоростью отряда пильщиков, с точностью ювелира.
Так и С. К черту все эти undefined behaviour, #pragma pack, ++i++ + i++, sizeof(**&(*i)->j), const * const * int.
Нужно заниматься делом! Придумывать стартапы, накидать на Ruby бэкэнд с интеграцией в соцсети, мобильное приложение под все телефоны. И не забыть добавить Bluetooth, потому что с ним всегда лучше.
2016 год уже заканчивается…
Единственное место, где стоит писать на С сейчас — это микроконтроллер ATTiny13A. Дешевый, маленький. 1КБ Flash, 64 байта RAM. Для остального С++ или еще более высокоуровневые языки.
Поверьте, поддерживать старый проект на С намного проще, чем на ассемблере.
Но чистый С использовать уже не имеет смысла. Компиляторы поддерживают С++, некоторые даже С++11. И не обязательно использовать динамическое выделение памяти, порой очень не хватает простого синтаксического сахара. Например, в С меня жутко выбешивает необходимость объявлять все переменные строго в начале функции. Какого черта я не могу написать for(int i=0; i < c; ++i)?
В языке С нет и никогда не было требования объявлять переменные строго в начале функции. Откуда вы это вязли?
Даже K&R С (не говоря уже о стандартизованном С) переменные всегда объявлялись в начале блока, а не в начале функции. Однако и это — далекое прошлое. В языке С переменные уже 17 лет как можно объявлять где угодно.
Другими словами вас "жутко выбешивает" вам же выдуманная чепуха, не имеющая никакого отношения к реальности.
разве что для терапии в специализированной психиатрической клинике для айтишников.
А что-то вроде ((void (*)(int))8)(42) довольно часто случается в микроконтроллерах.
В целом, это неверное утверждение.
>Но м.б. стоит заплатить отказом от трюков
Уже заплатили. Уже поколение, которое не знает трюков, на полном серьезе утверждает, что Ява или даже питон, быстрее чем С.
Мозгами заплатили.
И деньгами постоянно платим за непомерно жрущие ресурсы программы телефонов, телевизоров, итд
Нпр., недаром появление общедоступных многоядерных CPU вызвало появление TBB, которое не очень экономично по скорости.
что дешевле купить железо,
чем заплатить программистам за переделку, за доведение до ума, за тестирование и за выпуск более совершенного железа.
Экономика — она прагматична.
Она выбирает более дешевый путь.
В стародавние времена — когда железо было несопоставимо с трудом программистов дорогое — тогда и были нужны все эти трюки, край как нужны.
Голые абсолютные цифры — не важны. Какими бы страшными они не были.
Имеют значение только цифры в сравнении: «если мы пойдем таким путем — будут такие затраты, а если мы пойдем другим путем — будут иные затраты.»
Если бы затраты электроэнергии были бы выше, чем зарплата программистов — программистов бы заставили оптимизировать.
В этом и смысл саморегуляции в экономике.
Если бы затраты электроэнергии были бы выше, чем зарплата программистов — программистов бы заставили оптимизировать.
В таком виде это могло бы работать только в отношении продуктов «для внутреннего пользования». Ключевая проблема: энергонеэффективное ПО пишут ваши программисты, а платят за электроэнергию пользователи. Поэтому вы наблюдаете классическую «трагедию общин»: хотя более оптимизированное ПО могло бы снизить расходы на электричество и покупку новых ЭВМ (любого вида), результат вы увидите только если многие производители ПО начнут делать так же: достаточно многие, чтобы новый компьютер (не думаю, что ошибусь, сказав, что пользователи не имеют привычки определять, какое ПО жрёт энергию, если только это не пользователи техники, работающей от аккумуляторов) не требовался последней версии всех программ, которыми пользуется большинство пользователей.
Саморегуляция не работает в таких случаях, с «трагедией общин» может справиться только внешнее воздействие (к примеру, законы государства).
Так и будете дальше жонглировать символами пунктуации, пока с одной стороны нормальные люди напишут vhdl-схему и скомпилируют rt-код прямо в железо, а с другой стороны толпа юннатов напишет за три дня такой интерфейс для юзеров, что вам и не снилось, ни по срокам ни по качеству.
А пока в вашем т.н. простом языке т.н. программирования ловят undefined behavior светодиодами, на других простых языках, типа Оберона пишут операционные системы для того чипа, что чуть выше описали в vhdl-схеме таким кодом и с такими подходами, которые понятны пятикласснику. Страшовато, если пятиклассники поймут, что никакой магии нет.
О боже мой, в мире столько архитектур, и только язык Си может покрыть их все? Да хер там, нет никакого языка Си, нет никакой обещанной кроссплатформенности, есть только лицемерие и затыкание ртов. Жаль, что эволюции в ИТ нет, и она не отправила вас в Верхнюю Тундру.
>толпа юннатов напишет за три дня такой интерфейс для юзеров, что вам и не снилось, ни по срокам ни по качеству.
После проверки кода юннатов да, бывает, снится потом всякое. Но за переделку их работы неплохо платят, когда прижмет.
>на других простых языках, типа Оберона…
На Обероне и прочих языках (c#, d, rust,...) вполне себе можно писать системы — см.выше про Паскаль. Но пока что мы все пользуемся теми, что написаны на С.
>О боже мой, в мире столько архитектур, и только язык Си может покрыть их все
Да, Си есть практически везде. Потому что на нем строится все остальное.
>Жаль, что эволюции в ИТ нет, и она не отправила вас в Верхнюю Тундру
Я там был, там нефть и газ добывают. И софт там совсем не на Яве ))))
А пример кода от Яндекс по ссылке для компиляции… вы же не знаете наверняка, что код не был написан СПЕЦИАЛЬНО так, чтобы не компилироваться где попало. Или есть доказательство обратного? Нету — значит пример совершенно не подходит. А в результате — все выводы на этом примере построенные — ошибочны.
Это ПО управления кластером контейнеров. В этих контейнерах запускается много чего уже прикладного яндексовского.
Работает это нормально с какой-то из несвежих версий Ubuntu (не помню с какой точно).
Речь не идет о невозможности компиляции на другой операционной системе. Речь идет об той же самой Убунте, только более современной ее версии.
Вы утверждаете, что яндексовцы намерено заложили невозможность эксплуатации своего кластера на обновленных версиях все той же Ubuntu? Зачем?
Вы не в теме совершенно, если считаете, что сложный софт имеет столь незначительную зависимость как libstdc++.
От ядра там минимальная зависимость, компилироваться не хочет вовсе не из-за ядра.
Хорош теоретизировать — скомпилируйте хотя бы на свежем Debian, он ближайший родственник Ubuntu.
Корневой дефект языкой инфраструктуры С++ — она допускает создание кода, к которому необходимо приложить еще дополнительных усилий, чтобы обеспечить переносимость.
Нехилые такие дополнительные усилия, которые требуют специальных знаний. Программисту приходится тратить время не на решение прикладной задачи — а на борьбу со своим собственным инструментом.
Борьба со своим собственным инструментом — вот это и плохо.
Если бы не было других языков, где подобных проблем даже близко нет (к примеру, Go), я бы не стал называть это корневым дефектом языка.
Если интересно почему именно, то попробуйте самостоятельно скопилировать.
Меня не нужно спрашивать — я уже не помню точно на чем оно спотыкалось. Но помню, что не в одном и даже не в трех местах проблемы. Проблем непереносимости (даже на соседнюю версию Ubuntu) там — множество.
А как там у Java дела, оракловские машины виртуальные не сильно расходятся показаниях? Классы на PC и Android ведут себя одинаково?
А голанг, он уже вышел за приделы уютных x86-64 в народ? Он уже породил собственные системы окружения, или всё ещё «хороший-симпатичный», но чуть что — линкует плюсовые библиотеки и такой весь не при делах? «Это не мои ошибки, я не причём!»
Вы меня прям убиваете своим портированием. Назовите мне способ написать независимую от операционной системы прослойку для обращения к функционалу, специфичному для конкретной системы? Нет его.
На GoLang ещё не написали своих WxWidgets или Qt, а как только напишут, появятся такие же возмущения, что программа не собирается на разных версиях ОС, что ведёт себя криво и тп. Либо так, либо он навсегда останется любимой принцессой фриков, на которую можно только вожделенно смотреть, и которой из-за этого суждено умереть девственницей.
Хорош теоретизировать — скомпилируйте хотя бы на свежем Debian, он ближайший родственник Ubuntu.
Скомпилировал интереса ради, Debian 8 stable. Заняло 10 минут — 3 на то чтобы установить недостающие хедеры, 7 на то чтобы собственно скомпилировать, откачав по пути в swap 2Г данных из памяти.
— 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
ЧЯДНТ?
Вы только представьте себе, сколько электричества ВПУСТУЮ сжирает каждый запуск долбаного софта на сишарпе (компиляция псевдокода) в масштабах планеты!!!
Сколько?
(да, кстати, а сколько электричества впустую жрет компиляция никогда не использованных методов в языках без JIT? сколько теряется на недостаточной оптимизации без знания call path?)
Оговаривается минимальное значение бит: во‐первых, 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
; границы диапазонов допустимо раздвигать, но не сужать.
Перед вами — абстрактные дистилированные примеры, иллюстрирующие фундаментальные свойства языка. Как таковые, эти свойства языка используются в абсолютно всех программах на языке С без исключения.
А тут смысла применения конструкций подобных 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 бит.
Во-первых, язык С для компилятора GCC, который скрывается за ideone, начинается с флагов `-std=...` и `-pedantic-errors`. Без этих флагов компилятор GCC является компилятором развеселого студенческого языка для пивных вечеринок, никакого отношения к С не имеющего.
Во-вторых, фронтэнд ideone известен грубой фильтрацией диагностических сообщений, по каковой причине для демонстрации свойств языка всерьез рассматриваться не может.
Научитесь пользоваться хотя бы coliru
http://coliru.stacked-crooked.com/a/aef58ce98b518d02
Существуют и другие уважаемые онлайновые фронтэнды. Но не ideone.
Этот ядовитый комментарий был излишним, с остальным вынужден согласиться.
Вот С++ изначально ответственнее относится к контролю типов.
Во-первых, ни в коем случае. Язык С — это язык не ниже С99. Формально С11 — нынешний стандарт, но его можно назвать достаточно минорной модификацией С99 (ака "финтифлюшками"). Но С99 обсуждению не подлежит.
Во-вторых, как уже говорил выше, контроль типов всегда был строго идентичным в С и С++, за исключением возможности неявной конверсии из void *
в С. Да, современный С++ усилил контроль за конверсиями (за счет запрета narrowing conversions в ряде контекстов), но это ведь не то, что вы имели в виду, правда?
"Существовали платформы где он был по 6 бит (помните триграфы?)..." — это замечание, конечно, совершенно "мимо кассы". Особенности символьного набора (execution character set) конкретной платформы формально влияют на размет типа 'char' только тем, что обязаны в него помещаться. Не более того.
Даже в стариннейшем "C Reference Manual" тип 'char' уже постулировался, как знаковый 8-битный тип в формате 2's-complement. А дальше его спецификация становилась лишь еще более абстрактной и свободной.
Что касается использования 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<N>_t и [u]int_fast<N>_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 раз ввести универсальную конструкцию, её поддержка компиляторами потребует не слишком много усилий.
>У них-то как раз фиксированные размеры
Я имел в виду конструкцию, похожую на предложенную мной выше int<«N»>, с произвольной фиксированной разрядностью. Например int<16f>, int<18f>. А за int_fast_t спасибо, не знал, буду использовать.
Думаешь, ты знаешь Си?