Многие программисты утверждают, что знают С. Ну что ж, у него самый известный синтаксис, он существует уже 44 года и он не захламлен непонятными функциями. Он прост!
Я имею ввиду, что просто утверждать, что вы знаете С. Вероятно вы изучили его в институте или по ходу дела, скорее всего у вас есть какой-то опыт в его использовании, наверное вы думаете, что знаете его вдоль и поперек, потому что там не много-то надо знать. Вообще-то много. С не так прост.
Если вы думаете что он прост — пройдите этот тест. В нем всего 5 вопросов. Каждый вопрос в принципе одинаковый: какое будет значение возврата?
Поддержка публикации — компания Edison, которая разрабатывает системы электронных архивов и документооборота, а так же интегрирует софт и хард для промышленных предприятий.
В каждом вопросе есть 4 варианта ответа из которых один и только один является верным.
1
struct S{
int i;
char c;
} s;
main(){
return sizeof(*(&s));
}
А. 4
В. 5
С. 8
D. Я не знаю
2
main(){
char a = 0;
short int b = 0;
return sizeof(b) == sizeof(a+b);
}
А. 0
В. 1
С. 2
D. Я не знаю
3
main(){
char a = ‘ ‘ * 13;
return a;
}
А. 416
В. 160
С. -96
D. Я не знаю
4
main()
{
int i = 16;
return (((((i >= i) << i) >> i) <= i));
}
А. 0
В. 1
С. 16
D. Я не знаю
5
main(){
int i = 0;
return i++ + ++i;
}
А. 1
В. 2
С. 3
D. Я не знаю
Вот и все, положите свои ручки. Ответы последуют сразу после музыкальной паузы
Большая месса в С миноре Вольфганга Амадеуса Моцарта. Да, Моцарт тоже писал на С.
Да, правильный ответ к каждому вопросу «Я не знаю».
Теперь давайте разберемся с каждым из них.
Первый в действительности о структуре отступов. C компилятор знает что хранение невыравненных данных в RAM может быть дорогостоящим, поэтому он выравнивает ваши данные за вас. Если у вас есть 5 байт данных в структуре, вероятнее всего он сделает из них 8. Или 16. Или 6. Или сколько он хочет. Существуют такие расширения как GCC атрибуты aligned и packed которые позволяют вам получить некоторый контроль над этим процессом, но они не стандартизированные. Сам по себе C не определяет атрибуты отступов и поэтому верный ответ «Я не знаю».
Второй вопрос о integer promotion. Логично предположить, что тип
short int
и выражение, где наибольший тип тоже short int
будут одинаковыми. Но логичное не означает верное для С. Существует правило где каждое целое выражение продвигается до int
. Вообще-то все еще более запутанно. Загляните в стандарт, вам понравится.Но даже так, мы не сравниваем типы, мы сравниваем размеры. И единственная гарантия которую стандарт дает о размерах
short int
и int
в том, что предшествующий не должен быть больше последующего. Они вполне могут быть равными. И поэтому верный ответ «Я не знаю».Третий вопрос полностью о темных углах. Начиная с того, что ни переполнения integer ни наличие знака у типа
char
не определены стандартом. В первом случае у нас неопределенное поведение, во втором наличие знака зависит от конкретной реализации. Более того, размер типа char
в битах не определен. Существовали платформы где он был по 6 бит (помните триграфы?) и существуют платформы где все пять целочисленных типов по 32 бита. Без уточнения всех этих деталей каждое предположение о результате невалидное, поэтому ответ будет «Я не знаю».Четвертый вопрос выглядит коварно, но, оглядываясь назад, он не такой сложный, потому что вы уже знаете, что размер
int
не описывается стандартом. Он легко может быть 16 бит, тогда самая первая операция вызовет чрезмерный сдвиг и это обычное неопределенное поведение. Это не ошибка С, на некоторых платформах это даже не определено на уровне ассемблера, поэтому компилятор просто не может дать вам действующие гарантии без пожирания производительности.И поэтому снова верным ответом будет « Я не знаю».
И последний вопрос — классический. Ни порядок вычисления операндов для
+
ни даже порядок приоритета между операторами инкримента не определены, поэтому по существу каждая нетривиальная операция которая вовлекает i++
и ++i
является ловушкой, так как они изменяют своего операнда. Все может работать как вы и ожидаете на одной платформе и легко может сломаться на другой. Или нет. Вот она проблема неопределенных вещей. Когда вы встречаете такое, верный ответ всегда будет « Я не знаю».Большая месса в С миноре, написанная Вольфгангом Амадейсом Моцартом.
И на этом этапе я должен извиниться. Очевидно, что тест провокационный и может быть даже немного оскорбительный. Я приношу извинения если он вызвал какое-либо раздражение.
Дело в том, что я изучил С приблизительно в 1998 и на протяжении целых 15 лет думал, что я хорош в нем. Я выбрал этот язык в институте и реализовал некоторые успешные проекты на С на моей первой работе и даже тогда, когда я в основном работал на С++ я воспринимал его как чрезмерно раздутый С.
Поворотный момент настал в 2013 году, когда я участвовал в программировании критического для безопасности PLC. Это был исследовательский проект автоматизации на ядерной станции, где совершенно не принималась не полная спецификация. Я должен был осознать то, что хотя я знал многое о программировании на С, абсолютно бОльшая часть того что я знал было неверным. И это знание далось мне нелегко.
В конечном итоге мне пришлось научиться полагаться на стандарт вместо народной мудрости; доверять измерениям, а не предположениям, относиться к «вещам которые просто работают» скептически, — мне пришлось изучить подход технического проектирования. Это самое главное, а не какие-то конкретные WAT анекдоты.
Я надеюсь, что этот маленький тест поможет кому-то вроде меня из прошлого, узнать этот подход за 15 минут, а не 15 лет.
Перевод: Ольга Чернопицкая
Читать еще
Дональд Кнут о своей счастливой жизни, своей смерти и о последних двух проектах
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (269)
Amomum
11.11.2016 16:19+8На мой взгляд, для некоторых вопросов возможны более точные ответы:
1) А, В и С возможны (в зависимости от конкретных размеров типов).
2) A или В (потому что == возвращает или 0 или 1). Но не С (потому что == не может вернуть 2, стандарт это явно оговаривает)
3) А, B или С возможны, в зависимости от размера char и от его знаковости. Но если копнуть глубже, то тут вариант «Не знаю» действительно подходит лучше, потому что стандарт С не оговаривает кодировку символов! И код пробела, вообще-то, может быть любым (стандарт только обязывает его влезать в 5 бит).
4) Вопроса нет, поэтому я действительно не знаю.
5) Это не «я не знаю», это называется «неопределенное поведение».
Еще пара моментов:
— Почему-то паддинг в структурах вы называете «отступы» — никогда не слышал такого применения, обычно отступ — это отступ в начале строки исходного кода. Но допустим, с русскоязычной терминологией у меня плохо.
— «Более того, размер типа char в битах не определен.» Вообще-то, он определен в макросе CHAR_BITS. Вы, видимо, имели в виду, что он не определен в стандарте — это так.jcmvbkbc
12.11.2016 02:26+25) Это не «я не знаю», это называется «неопределенное поведение».
Вопрос был не «что это», а «что вернёт программа».
ColdPhoenix
11.11.2016 16:23+9опрос скорее о том «знаете ли вы стандарт».
на практике мы работаем на определенных платформах и компиляторах, и их поведение нам известно.
итого. верные ответы должны быть такими:
1)зависит от окружения и настроек выравнивания.
2)зависит от окружения.
3,4)опять же зависит от окружения, либо UB.
5)UB.
в общем-то таких примеров можно массу подобрать
получился опрос для школьников… с понурыми ответами у доски вида «нууу я не знаю...»
надеялся на что-то более серьезное, а не про сферических коней в вакууме.
lair
11.11.2016 16:24+27Великая месса в С миноре Вольфганга Амадеуса Моцарта. Да, Моцарт тоже писал на С.
Вас обманули, причем дважды. Во-первых, по-русски это называется до-минор. Надеюсь, на языке "До" Моцарт не писал. Во-вторых, в латинизированной нотации до-минор — это c (прописная буква), а C (заглавная буква) — это до-мажор.
Бонус: похожий на букву C знак в начале каждого нотного стана (после ключевых знаков) — это разорванная окружность, обозначающая размер 4/4 (и происходящая из мензуральной нотации, где она означала несовершенный, т.е. четно-дольный метр).
(Ну и еще по-русски это называется "Большая месса")
Alert123
11.11.2016 16:25Я бы не взял программиста который такие трюки будет использовать в реальном коде.
TheCalligrapher
14.11.2016 01:14-3Видимо поэтому вам никогда не доведется принимать такие решения.
Это не трюки, а фундаментальные свойства С, как платформы. Это атомы, из которых состоит С. Абсолютно любая программа на языке С опирается на эти «трюки».Antervis
14.11.2016 05:18+3Нет. Ни одна программа не должна опираться на неопределенное поведение
TheCalligrapher
14.11.2016 06:08В моем комментарии нет ни слова о том, что программы «должны опираться на неопределенное поведение».
Antervis
14.11.2016 06:52-1все эти трюки являются примерами неопределенного поведения. Или, как минимум, неоднозначно/контекстуально определенного/платформозависимого поведения.
TheCalligrapher
14.11.2016 06:57Во-первых, это оголтелое вранье. Никакого неопределенного поведения в первом и втором примерах нет. Третий и четвертый смогут содержать его потенциально, но речь там идет не о том. Явному неопределенному поведению посвящен только пятый пример.
Во-вторых, темой этой дискуссии является избежание неопределенного поведения, а не некое "опирание" на него. Я по-моему ясно это постулировал в комментариях выше. Внимательнее читаем комментарии.
Antervis
14.11.2016 07:20+1вы написали:
Абсолютно любая программа на языке С опирается на эти «трюки».
Часть «трюков» — примеры неопределенного поведения, программы без ошибок его не содержат. Остальная часть — неоднозначности, закладываться на которые в общем случае нельзя.
RomanArzumanyan
11.11.2016 16:28+3Есть мнение, что при написании критически важных вещей первичным будет знание не стандарта, а целевых компилятора и железа, ибо разработчики оных могли забить на стандарт.
vvzvlad
11.11.2016 20:10Ну, много проблем не с тем, что разработчики забивают на стандарт, а с тем, что они реализуют по-разному то, что не регламентируется жестко стандартом.
Если использовать только то, что описывает стандарт без всяких(пусть и общеупотребительных) трюков, то вероятность что-то сломать гораздо меньше, а если и сломается — всегда можно попенять разработчикам компилятора, что они неправильно реализовывают стандарт.RomanArzumanyan
11.11.2016 22:04+1Стандарт Си в области целочисленной арифметики — это большая боль. Он слишком поздно появился для такого важного языка.
Siemargl
11.11.2016 23:44В этом направлении боли не видел я, кроме тривиального 3/2.
Есть другие прорехи, конечно.
lieff
11.11.2016 16:31+3Сначала воспринял ответ «Я не знаю», как шуточный заведомо неверный, типа сдался. «Не определено» aka UB тут больше подходит. Как только это понял — на все ответил верно.
ns5d
11.11.2016 16:44+2Варианты ответов составлены плохо, а именно «Я не знаю». Я трактовал его как признание, что я не знаю С. Правильно было бы написать «Неопределенное поведение». Тогда результаты ответов были бы другими.
GREGOR_812
11.11.2016 17:22-4В последнем случае на самом деле всё понятно
Рассмотрим два пути:
1. правый операнд при сложении вычисляется первым
Тогда наше выражение пройдёт следующий путь
> i = 0;
> 0++ + ++0 -> 1++ + 1 -> 2 (i = 2)
2. левый операнд вычисляется первым
> i = 0;
> 0++ + ++0 -> 0 + ++1 -> 2 (i = 2)
и результат равен 2, и значение i равно 2LynXzp
11.11.2016 17:38+3В зависимости от компилятора (хотя могут все и вычислять одинаково, но не обязаны). Это неопределенное поведение по стандарту «Если программа пытается модифицировать одну переменную дважды не пересекая точку следования, то это ведет к undefined behavior. Так говорит Стандарт.» http://alenacpp.blogspot.com/2005/11/sequence-points.html
GREGOR_812
11.11.2016 18:09LynXzpSingerofthefall как это противоречит моему утверждению?
LynXzp
11.11.2016 19:26Это говорит о том что Ваше утверждение не обязательно верное, точнее результат выполнения программы такой, но только сегодня, и только в этой версии компилятора (может еще в других). Сегодня такой код выдает 2. А завтра благодаря side-effects оптимизаций компилятора он может выдать и 1 и 3 (а может еще что-то). Разработчики компиляторов не обязаны делать так чтобы результат получался 2. И если это будет выгодно в угоду другим оптимизациям они изменят логику и результат будет другой.
1(i++) + (++i) =(компилятор параллельного мультискаляного процессора считает что можно вычислить результат скобок независимо, а потом сложить) = 0 + 1 = 1
Как получить три, не придумал, но в более сложных и запутанных ситуациях может получится и больше разных ответов. Причем они все не будут багами, потому что UB.
Singerofthefall
11.11.2016 17:48+1Это потому что автор неправильно объяснил причину. В 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.
SBKarr
11.11.2016 18:46+4А вот GCC 4.2 (если не ошибаюсь) с -03 выдавал 1. Объясняю на пальцах.
Логика такая: компилятор принял i за константу внутри точки следования и оптимизировал левый инкремент до возврата нуля, а сам инкремент поставил в конец точки следования (и отбросил за ненадобностью, ведь дальше тела функции i не используется). После чего выполнил правый инкремент, сложение и вернул результат.TheCalligrapher
14.11.2016 06:25+1Это не более чем частный случай проявления неопределенного поведения.
TrueBers
11.11.2016 17:24+2Существуют такие расширения как GCC атрибуты aligned и packed которые позволяют вам получить некоторый контроль над этим процессом, но они не стандартизированные.
Чё это они не стандартизированы? Уже лет 5 как.
C11 ISO Standard, 6.7.5 Alignment specifier.
April 2011.
slonm
11.11.2016 17:40+2Все UB не от скудоумия авторов стандарта, а от стремления избежания лишних действий в рантайме.
Хотя можно было бы и разработать несколько подмножеств стандарта и, соответственно, режимов компиляции. Например «легкий» — то что сейчас и «строгий» — рантайм из-за проверок утяжеляется, но UB минимизируются
dnf
11.11.2016 17:57-1Ответил «я не знаю» на первые два вопроса — интуитивно отгадал правильный ответ на остальные три, даже не читая вопросов =) Итого 5.
vastzp
11.11.2016 18:30+7Ваш тест пройдет даже моя бабушка!
armature_current
11.11.2016 19:28+2Я бы рискнул сделать ставку на то, что 99% бабушек смогут пройти этот тест!
SBKarr
11.11.2016 18:31+6Вапще когда я вижу int, short, long и прочее подобное у меня уже автоматически возникает вопрос: а архитектура то какая?
В целом для своего времени не привязывать типы к размерам, возможно, было неплохой идеей. Ибо бывали платформы и 16-битные, и 32-битные, и у каждого типа процессора были свои оптимальные размеры для целочисленных инструкций, которые разработчик процессора мог рекомендовать автору компилятора — это делало программы быстрее, причём, с теми рабочими частотами, ощутимо быстрее.
Что до UB по любому странному поводу — дык, не с проста. Каждое описанное UB это зарытый где-то рядом повод для оптимизации. В условиях нехватки процессорного времени тоже отличное решение.
Беда только в том, что теперь процессорного времени на человека есть целая гора, это позволяет нам запускать тяжеленные софтины на прожорливой Java, обрабатывать запросы к веб-серверам на интерпретируемых языках и делать много других странных вещей, недопустимых во времена создания C. Возможно, стоит сместить фокус системного программирования с оптимальности на безопасность?AVI-crak
12.11.2016 02:38Очевидно что Intel. Эта зараза от туда тянется.
Я уже давно привык юзать конкретные чёткие типы, как например uint32_t, int8_t и так далее.
Для математики тоже есть свои типы с чёткими размерами, от float64_t до float32_t, мелочь не используется -потому как считается в тиках намного дольше.
Насчёт выравнивания структур при оптимизации. Самый простой способ — выполнить объединение двух заведомо соразмерных структур. Для запрета перемешивания — объединение с перехлёстом.
Подобные знания требуются программисту который пишет на уровне железа — это конкретное обращение к регистрам в памяти. Всем остальным — лишний багаж знаний.
Это хорошо что ещё не затронули тему передачи параметров в функцию. Для разных архитектур ядра — параметры могут передаваться самым удивительным способом. С лева на право, с право налево, через регистры ядра, через теневые регистры ядра (не знали? :) ), через стек, через внешнюю память.
Зоопарк.ZyXI
12.11.2016 18:58Архитектура ядра оказывает влияние на calling convention, но это не единственный определяющий фактор. ОС (если есть) или компилятор (если нет) могут из каких?то соображений выбрать не самое оптимальное соглашение, которому будут следовать. (В принципе, компилятор может даже проигнорировать соглашение о способе вызова, принятое в определённой ОС, но по понятным причинам (взаимодействие с чужими библиотеками), скорее всего, не будет этого делать.)
grossws
16.11.2016 23:57+1Или наоборот явно будет использовать несовместимый cc при использовании какого-нибудь stdcall или, например, cilkplus. Calling conventions, например, могут зависеть от набора инструкций или выбранного float-abi.
Antervis
12.11.2016 18:21не забывайте, что есть научный софт, софт для анализа больших объемов данных, как пример из собственной практики могу привести обработку СВЧ радиосигналов. Там очень важно, чтобы работало как можно быстрее.
Siemargl
11.11.2016 18:37-2Для программиста на С нет ответа «Я не знаю». Правильный ответ — на моей платформе (ОС+компилятор) ответ будет конкретно таким и таким.
Хотя я и ошибся с promote to int во втором примере )LynXzp
11.11.2016 19:39+3На 4-й пример GCC 4.2 -O3 выдает ответ 1, а GCC 4.8 с любой оптимизацией выдает 2.
Все такие исключительные ситуации для своей платформы и компилятора и его версии не запомнить (и не узнать) коммент в этом топике — см цитату.
Я раньше пользовался… К примеру переполнением uint8_t для счетчика циклического буфера чтобы избавится от всяких if, но чем больше узнаю, тем больше удивляюсь.jpg и стараюсь так не делать.Siemargl
11.11.2016 20:34Наверное имелся в виду 5й. Там правильный ответ — так делать не надо, UB.
Но опять же, есть определенный перечень UB, на которые ты знаешь, как будет у тебя, но все равно сознательно пишешь нормально.
jcmvbkbc
12.11.2016 02:30Я раньше пользовался… К примеру переполнением uint8_t для счетчика циклического буфера чтобы избавится от всяких if
А почему перестали? У uint8_t никаких проблем быть не должно.LynXzp
12.11.2016 13:20+2Переполнение, кажется самым популярным используемым UB с ожидаемым результатом. И хотя «разработчики компиляторов не звери» и так работает процессор (контроллер), но на всякий случай. Переполнение это UB. А если поведение UB может меняться от версии к версии gcc, то на всякий случай лучше не надо.
Andrey2008 из PVS-Studio утверждает с переполнением лучше не шутитьОбычно глупости выглядят как-то так (собирательный образ):
(Хотя в статье проблема в неправильном типе, и переполнение работает так как ожидается. И речь в статье не про UB при переполнении, но думаю у него опыта и знаний больше чем у меня.)
Ну да, формально переполнение 'int' приводит к неопределенному повреждению. Но это не более чем болтовня. На практике, всегда можно сказать что получится.
Можете считать это религией или стилем. Но мне кажется он хорош. Если нет острой необходимости, то не использую хаки (и goto).jcmvbkbc
12.11.2016 13:39+1Переполнение, кажется самым популярным используемым 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.
TheCalligrapher
13.11.2016 22:52Очень зря. Свойства uint8_t (если таковой тип предоставляется реализацией) четко оговорены спецификацией языка и никакой неоднозначности не допускают. "Зацикленное" (модульное) поведение беззнаковых типов — очень полезная фича языка, пользоваться которой можно и нужно. Поэтому ваше "чем больше узнаю", похоже, привело лишь к тому, что с водой вы выплеснули и ребенка.
LynXzp
14.11.2016 01:13Спасибо Вам и jcmvbkbc. Был неправ. «Переполнение» беззнаковых типов безопасно и всегда будет одинаково. Я этого не знал и сильно удивился, чего это вдруг знаковое переполнение — UB, а беззнаковое безопасно. Пытался понять читая стандарт и форумы — не дало много толку, переполнение не только «implementation dependent» как могло бы быть, но и «undefined behavior», а беззнаковое определено. Хотя стандарт написан прямо юридическим языком, трудно понять (а причины и подавно). Картинка удивляюсь.jpg все еще актуальна.
Ну в любом случае — если не знаешь можно перестраховаться, а знаешь четкую границу — можно и делать финт ушами и быть уверенным в безопасности. Спасибо.
crea7or
11.11.2016 19:27А первый вопрос любят на собеседованиях спрашивать, только, конечно, пишут, какое выравнивание установлено.
demitel
11.11.2016 20:02Когда смотрел вопросы, в голове был ответ «зависит от платформы и/или реализации компилятора».
Когда смотрел предлагаемые ответы, среди них не было правильного. Потому что ответ «Я не знаю» в данном случае не отражает реалий.
Поэтому это не тест, а подвох из серии «На грядке сидело 3 воробья, одного поймала кошка, сколько воробьёв осталось?».
То, что в С куча тёмных, платформозависимых и компиляторозависимых моментов, совершенно не удивляет, а удивляет, что автор за 15 лет об этом не догадывался…
Crypt0r
11.11.2016 20:30+1Но даже так, мы не сравниваем типы, мы сравниваем размеры. И единственная гарантия которую стандарт дает о размерах short int и int в том, что предшествующий не должен быть больше последующего.
В добавок к этому: оператор sizeof возвращает не размер операнда, а его пропорциональное (или как это правильно сказать) отношение к размеру char. То есть sizeof(int) == 4 еще не означает что int равен четырем байтам. Это означает только что в int может вместиться четыре char'а (Wikipedia).deviant_9
13.11.2016 13:03То есть 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.
С другой стороны, стандарт Си описывает всё в терминах абстрактной машины, не привязываясь ни к каким свойствам реальных машин. В Си своя модель памяти, которая не обязана сколько-нибудь явно соответствовать организации памяти той реальной среды, в которой программа будет выполняться (собственно, и не соответствует: компиляторы одни переменные помещают в регистры, другие полностью выкидывают, переупорядочивают операции чтения/записи и т. д.). Поэтому и понятие «байт» в стандарте Си, строго говоря, не обязано совпадать с понятием байта в «железе».
Siemargl
11.11.2016 20:39На самом деле, по теме статьи можно расширить свой список «я этого не знал» на сайтике тестов http://www.quizful.net.
Так сколько то в день попыток бесплатных, хватит чтобы оценить уровень пола.
ittakir
11.11.2016 21:00Я все-таки считаю, что язык программирования должен быть в первую очередь инструментом достижения цели.
Хорошо, если этот инструмент будет удобным в работе. Тогда и результат будет быстрее, и, возможно, качественнее.
Как правило, современным инструментом работать намного эффективнее. Вместо лучковой пилы лучше взять электролобзик. И не нужно думать, как раньше, над углом заточки зубцов пилы, затачивать их вручную. Сейчас просто идешь в магазин, покупаешь пилку «по дереву», вставляешь в лобзик, и пилишь со скоростью отряда пильщиков, с точностью ювелира.
Так и С. К черту все эти undefined behaviour, #pragma pack, ++i++ + i++, sizeof(**&(*i)->j), const * const * int.
Нужно заниматься делом! Придумывать стартапы, накидать на Ruby бэкэнд с интеграцией в соцсети, мобильное приложение под все телефоны. И не забыть добавить Bluetooth, потому что с ним всегда лучше.
2016 год уже заканчивается…CodeRush
11.11.2016 21:22+2Пока вы там сверху придумываете стартапы и накидываете бэкэнд на Руби, разработчики на С разрабатывают вашу пилу и решают проблему с медведями. Это тоже дело, и оно должно быть сделано прежде, чем на вашей системе вообще можно будет запустить Руби и соединить ее с интернетом, в любом году.
ittakir
12.11.2016 09:00Это само собой. Но Winsock была написана сто лет назад. Работа с файлами тоже. Все, это больше не надо писать. Используйте результат труда тех людей, кто писал на С. Но сами пишите на более высокоуровневых языках. Иначе не видать нам технологической сингулярности, как своих ушей!
Единственное место, где стоит писать на С сейчас — это микроконтроллер ATTiny13A. Дешевый, маленький. 1КБ Flash, 64 байта RAM. Для остального С++ или еще более высокоуровневые языки.Khasuist
12.11.2016 17:44Для мелких микроконтроллеров стоит писать на ассемблере, тогда вы больше напишете, быстрее напишете, быстрее отладите то что написали и будете точно знать что происходит с вашим кодом в проце.
ittakir
12.11.2016 19:06-1Ассемблер у каждого микроконтроллера разный. Лучше использовать универсальный ассемблер — язык С.
Поверьте, поддерживать старый проект на С намного проще, чем на ассемблере.
Но чистый С использовать уже не имеет смысла. Компиляторы поддерживают С++, некоторые даже С++11. И не обязательно использовать динамическое выделение памяти, порой очень не хватает простого синтаксического сахара. Например, в С меня жутко выбешивает необходимость объявлять все переменные строго в начале функции. Какого черта я не могу написать for(int i=0; i < c; ++i)?Antervis
12.11.2016 20:28+2а разве это требование не было убрано в стандарте с89?
ittakir
13.11.2016 08:09-1Может быть, но Freescale CodeWarrior IDE не в курсе этого.
TheCalligrapher
13.11.2016 19:59Во-первых, при чем здеcь "IDE" не совсем ясно. Все таки это определяется компилятором, а не IDE.
Во-вторых, вы фантазируете. В CodeWarrior нет и никогда не было требования "объявлять все переменные строго в начале функции".
TheCalligrapher
13.11.2016 19:57В языке С нет и никогда не было требования объявлять переменные строго в начале функции. Откуда вы это вязли?
Даже K&R С (не говоря уже о стандартизованном С) переменные всегда объявлялись в начале блока, а не в начале функции. Однако и это — далекое прошлое. В языке С переменные уже 17 лет как можно объявлять где угодно.
Другими словами вас "жутко выбешивает" вам же выдуманная чепуха, не имеющая никакого отношения к реальности.
unabl4
11.11.2016 23:32Это всё прекрасно, только стандартный руби до сих пор на шее С сидит и в обозримом будущем с него слезать вообще не собирается :) Как, впрочем, и некоторые другие интерпретируемые языки. А делами нужно заниматься, да, только каждый своим — кто руби, кто явой, а кто и си. Главное в гармонии сосуществовать :)
Antervis
12.11.2016 18:16да вам в жизни не понадобится #pragma pack в том, что можно написать на питоне/джаве/руби. Остальное является головоломками и/или примерами ub, хотя в реальном коде встречается раз в декаду и то скорее в виде ошибки.
u_w
12.11.2016 00:13+4чесно, не понимаю где может применяться код выложенный в вопросах…
разве что для терапии в специализированной психиатрической клинике для айтишников.SBKarr
12.11.2016 00:52-2Ну, например, вот в таких вариантах: https://github.com/cocos2d/cocos2d-x/blob/v3/cocos/renderer/CCTexture2D.cpp#L131 (Вопрос к публике, а вы с какого раза въехали в порядок операций в выражении?). Добавить инкремент по другую сторону присваивания, и будет практически вариант за номером 5.
А что-то вроде ((void (*)(int))8)(42) довольно часто случается в микроконтроллерах.Siemargl
12.11.2016 02:19Про https://github.com/cocos2d/cocos2d-x/blob/v3/cocos/renderer/CCTexture2D.cpp#L131 очень примитивный подкол.
Это стандартное обращение, начиная с 1го класса изучения С: *pc++=c;
third112
12.11.2016 14:46+1С большим интересом прочел статью и все комметы (на данный момент). ИМХО сделано много верных замечаний про зоопарки (платформ, систем, компиляторов), сказано много правильных слов про необходимость знать стандарты языка, особенности железа и компилятора, на котором работаешь. Но как быть с переносом кода? Где-то, когда-то, кто-то на антикварных сейчас железе, системе, компиляторе (ЖСК) написал нужный мне код, но я могу не знать то ЖСК, тогда вероятно, что используя тот код отгребу кучу багов, которые мне трудно будет фиксить. Чем дальше в лес — тем больше дров: зоопарк ЖСК растет. Для долговременной преемственности кода нужен достаточно строгий язык типа стандартного Паскаля. Пусть ОО Паскаля. Конечно, за все приходится платить. Си гораздо более гибкий чем Паскаль, и даже турбо, и даже Дельфи. Многие трюки будут невозможны. Но м.б. стоит заплатить отказом от трюков? А когда они очень нужны, то тогда и только тогда использовать Си и/или ассемблер?
Siemargl
12.11.2016 15:18>Си гораздо более гибкий чем Паскаль, и даже турбо, и даже Дельфи.
В целом, это неверное утверждение.
>Но м.б. стоит заплатить отказом от трюков
Уже заплатили. Уже поколение, которое не знает трюков, на полном серьезе утверждает, что Ява или даже питон, быстрее чем С.
Мозгами заплатили.
И деньгами постоянно платим за непомерно жрущие ресурсы программы телефонов, телевизоров, итдthird112
12.11.2016 15:51Использование С/С++ тоже не всегда способствует экономии ресурсов.
Нпр., недаром появление общедоступных многоядерных CPU вызвало появление TBB, которое не очень экономично по скорости.0xd34df00d
12.11.2016 20:32Потому что TBB вынуждено быть универсальным паттерном, а в отдельных приложениях специальные паттерны могут быть быстрее.
polifill
12.11.2016 16:25+2Платим только потому,
что дешевле купить железо,
чем заплатить программистам за переделку, за доведение до ума, за тестирование и за выпуск более совершенного железа.
Экономика — она прагматична.
Она выбирает более дешевый путь.
В стародавние времена — когда железо было несопоставимо с трудом программистов дорогое — тогда и были нужны все эти трюки, край как нужны.Siemargl
12.11.2016 19:22А не потому ли, что экономике выгоднее _продать_ еще раз зайца в новой одежке?
polifill
12.11.2016 20:43При этом лично ты как программист получаешь деньги. И профессия программиста не самая низкооплачиваемая.
Так что это выгодно тебе как программисту. Выгодно лично тебе.
А не какой-то там абстрактной экономике.
Antervis
12.11.2016 20:29а теперь представьте лишние затраты на электроэнергию, связанные с неэффективными алгоритмами.
polifill
12.11.2016 20:48Ты не понял что такое «практичная экономика»
Голые абсолютные цифры — не важны. Какими бы страшными они не были.
Имеют значение только цифры в сравнении: «если мы пойдем таким путем — будут такие затраты, а если мы пойдем другим путем — будут иные затраты.»
Если бы затраты электроэнергии были бы выше, чем зарплата программистов — программистов бы заставили оптимизировать.
В этом и смысл саморегуляции в экономике.
ZyXI
12.11.2016 23:59Если бы затраты электроэнергии были бы выше, чем зарплата программистов — программистов бы заставили оптимизировать.
В таком виде это могло бы работать только в отношении продуктов «для внутреннего пользования». Ключевая проблема: энергонеэффективное ПО пишут ваши программисты, а платят за электроэнергию пользователи. Поэтому вы наблюдаете классическую «трагедию общин»: хотя более оптимизированное ПО могло бы снизить расходы на электричество и покупку новых ЭВМ (любого вида), результат вы увидите только если многие производители ПО начнут делать так же: достаточно многие, чтобы новый компьютер (не думаю, что ошибусь, сказав, что пользователи не имеют привычки определять, какое ПО жрёт энергию, если только это не пользователи техники, работающей от аккумуляторов) не требовался последней версии всех программ, которыми пользуется большинство пользователей.
Саморегуляция не работает в таких случаях, с «трагедией общин» может справиться только внешнее воздействие (к примеру, законы государства).
pengyou
12.11.2016 17:50-3Вы просто престарелый фокусник, который не заметил, как алхимия разделилась на разные науки.
Так и будете дальше жонглировать символами пунктуации, пока с одной стороны нормальные люди напишут vhdl-схему и скомпилируют rt-код прямо в железо, а с другой стороны толпа юннатов напишет за три дня такой интерфейс для юзеров, что вам и не снилось, ни по срокам ни по качеству.
А пока в вашем т.н. простом языке т.н. программирования ловят undefined behavior светодиодами, на других простых языках, типа Оберона пишут операционные системы для того чипа, что чуть выше описали в vhdl-схеме таким кодом и с такими подходами, которые понятны пятикласснику. Страшовато, если пятиклассники поймут, что никакой магии нет.
О боже мой, в мире столько архитектур, и только язык Си может покрыть их все? Да хер там, нет никакого языка Си, нет никакой обещанной кроссплатформенности, есть только лицемерие и затыкание ртов. Жаль, что эволюции в ИТ нет, и она не отправила вас в Верхнюю Тундру.Siemargl
12.11.2016 19:18Я не утверждаю, что надо все писать на С, я лишь сказал, что трюки знать надо (да, пятиклассникам).
>толпа юннатов напишет за три дня такой интерфейс для юзеров, что вам и не снилось, ни по срокам ни по качеству.
После проверки кода юннатов да, бывает, снится потом всякое. Но за переделку их работы неплохо платят, когда прижмет.
>на других простых языках, типа Оберона…
На Обероне и прочих языках (c#, d, rust,...) вполне себе можно писать системы — см.выше про Паскаль. Но пока что мы все пользуемся теми, что написаны на С.
>О боже мой, в мире столько архитектур, и только язык Си может покрыть их все
Да, Си есть практически везде. Потому что на нем строится все остальное.
>Жаль, что эволюции в ИТ нет, и она не отправила вас в Верхнюю Тундру
Я там был, там нефть и газ добывают. И софт там совсем не на Яве ))))
Khasuist
12.11.2016 16:58-1Подавляющее большинство любителей сишарперов и джав НЕ представляют себе что делает в компе их программа в действительности. Доказательство — малое количество неглючных программ на этих языках. Зато быстро в разработке, пусть и жрёт ресурсы — не свои же они, а пользователей… Вы только представьте себе, сколько электричества ВПУСТУЮ сжирает каждый запуск долбаного софта на сишарпе (компиляция псевдокода) в масштабах планеты!!!
А пример кода от Яндекс по ссылке для компиляции… вы же не знаете наверняка, что код не был написан СПЕЦИАЛЬНО так, чтобы не компилироваться где попало. Или есть доказательство обратного? Нету — значит пример совершенно не подходит. А в результате — все выводы на этом примере построенные — ошибочны.polifill
12.11.2016 17:54По Яндексу:
Это ПО управления кластером контейнеров. В этих контейнерах запускается много чего уже прикладного яндексовского.
Работает это нормально с какой-то из несвежих версий Ubuntu (не помню с какой точно).
Речь не идет о невозможности компиляции на другой операционной системе. Речь идет об той же самой Убунте, только более современной ее версии.
Вы утверждаете, что яндексовцы намерено заложили невозможность эксплуатации своего кластера на обновленных версиях все той же Ubuntu? Зачем?Antervis
12.11.2016 18:08при чем тут вообще ubuntu? На запускаемость программ на с++ на линуксе влияют в первую очередь версия libstdc++, во вторую — версия ядра.
polifill
12.11.2016 18:32Речь о https://github.com/cocaine/cocaine-core
Вы не в теме совершенно, если считаете, что сложный софт имеет столь незначительную зависимость как libstdc++.
От ядра там минимальная зависимость, компилироваться не хочет вовсе не из-за ядра.
Хорош теоретизировать — скомпилируйте хотя бы на свежем Debian, он ближайший родственник Ubuntu.
Antervis
12.11.2016 20:54Речь о том, что от языка (точнее gcc реализации) зависит только libstdc++, которая уже 10 лет как backward compatible, всё остальное — стороннее ПО. Не компилится код яндекса или еще чей-то там? Язык то тут при чем?
polifill
12.11.2016 21:00Под языком я подразумеваю всю инфраструктуру использования языка — включая библиотеки и компиляторы и синтаксис и различные стандарты…
Корневой дефект языкой инфраструктуры С++ — она допускает создание кода, к которому необходимо приложить еще дополнительных усилий, чтобы обеспечить переносимость.
Нехилые такие дополнительные усилия, которые требуют специальных знаний. Программисту приходится тратить время не на решение прикладной задачи — а на борьбу со своим собственным инструментом.
Борьба со своим собственным инструментом — вот это и плохо.
Если бы не было других языков, где подобных проблем даже близко нет (к примеру, Go), я бы не стал называть это корневым дефектом языка.
Если интересно почему именно, то попробуйте самостоятельно скопилировать.
Меня не нужно спрашивать — я уже не помню точно на чем оно спотыкалось. Но помню, что не в одном и даже не в трех местах проблемы. Проблем непереносимости (даже на соседнюю версию Ubuntu) там — множество.iCpu
13.11.2016 00:03+1Пардонте, вы выше голосовали за python, java и иже с ними, но… А как там зоопарк у питона? Я уже могу скачать .py файл и запустить его, или мне нужно проверить, это второй или третий? Как там у интерпретаторов второго фланга с совместимостью? Я могу скачать openBLAS и запустить его на любом интерпретаторе? Нет?
А как там у Java дела, оракловские машины виртуальные не сильно расходятся показаниях? Классы на PC и Android ведут себя одинаково?
А голанг, он уже вышел за приделы уютных x86-64 в народ? Он уже породил собственные системы окружения, или всё ещё «хороший-симпатичный», но чуть что — линкует плюсовые библиотеки и такой весь не при делах? «Это не мои ошибки, я не причём!»
Вы меня прям убиваете своим портированием. Назовите мне способ написать независимую от операционной системы прослойку для обращения к функционалу, специфичному для конкретной системы? Нет его.
На GoLang ещё не написали своих WxWidgets или Qt, а как только напишут, появятся такие же возмущения, что программа не собирается на разных версиях ОС, что ведёт себя криво и тп. Либо так, либо он навсегда останется любимой принцессой фриков, на которую можно только вожделенно смотреть, и которой из-за этого суждено умереть девственницей.
Antervis
13.11.2016 00:34Без конкретных примеров сложно даже понять в чем суть проблемы. Обычно когда я пытаюсь написать что-то непереносимое, это начинается либо с -std=gnu++11/14, либо с #include <windows.h>
jcmvbkbc
13.11.2016 05:28+1Хорош теоретизировать — скомпилируйте хотя бы на свежем 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
Khasuist
12.11.2016 19:29Я утверждаю, что я не знаю мотивов яндексовцев на то или иное действие, а по сему допускаю возможность. И откуда вы знаете, что у них работает именно этот код? Слишком много вариантов! Вы же ни того ни другого не знаете, и не докажете. А причин может быть гораздо больше.
polifill
12.11.2016 20:54Я просто спросил знакомых ребят из Яндекса, они как раз имеют касательство к эксплуатации этого кода.
У них работает именно этот код. И именно на Ubuntu (версию уже не помню).
Завязывай беспочвенно фантазировать — твое бесполезное словоблудие не интересно.
lair
13.11.2016 13:34Вы только представьте себе, сколько электричества ВПУСТУЮ сжирает каждый запуск долбаного софта на сишарпе (компиляция псевдокода) в масштабах планеты!!!
Сколько?
(да, кстати, а сколько электричества впустую жрет компиляция никогда не использованных методов в языках без JIT? сколько теряется на недостаточной оптимизации без знания call path?)
Shamov
12.11.2016 17:53По ходу я лучше всех знаю Cи. Прочитав только первый вопрос, я уже знал, что на все вопросы будет ответ «не знаю». Остальные вопросы я прочитал просто из любопытства.
Antervis
12.11.2016 18:11насколько я знаю, размер char фиксирован стандартом, а int'ом с размером 4 может быть литерал типа 'a' в си. (не с++)
TheCalligrapher
13.11.2016 18:37Ширина типа 'char' — то есть количество значащих битов в нем — не оговаривается стандартом языка. Результат же 'sizeof(char)' — гарантированно 1. То есть тип 'char' — это единица измерения размеров всех остальных типов, это «байт» в понимании языка С. Однако диапазон значений этого типа не фиксирован.
ZyXI
13.11.2016 20:06Оговаривается минимальное значение бит: во?первых,
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
; границы диапазонов допустимо раздвигать, но не сужать.
FGV
12.11.2016 18:26+1а кто нибудь может привести примеры использования подобных абракодабр в своем (чужом) коде? из всех приведенных примеров как то сомнительно их осмысленное применение.
Antervis
13.11.2016 00:36осмысленное применение ub? Я даже не могу представить почему это звучит сомнительно…
TheCalligrapher
13.11.2016 21:13-1Перед вами — абстрактные дистилированные примеры, иллюстрирующие фундаментальные свойства языка. Как таковые, эти свойства языка используются в абсолютно всех программах на языке С без исключения.
FGV
15.11.2016 16:31Проведу аналогию с электроникой. В книжке «Искусство схемотехники» после описания соответствующего элемента приводится два набора примеров «Удачные схемы» и «Негодные схемы». И те и те отражают «возможности» и самое главное — реализуют некую задумку (подписано что эта схема делает и для чего предназначена), только вторые показывают как делать не надо.
А тут смысла применения конструкций подобных 1-5 вообще не видно.TheCalligrapher
15.11.2016 20:16-1Вы по прежнему не понимаете, о чем речь. Конструкции 1-5 в таком чистом виде не имеют никакого применения. Они лишь демонстрируют важные фундаментальные факты и языке. На эти фундаментальные факты опираются все программы на С без исключения. Это не "схемы", удачные или неудачные, это иллюстрации Закона Ома.
ZyXI
12.11.2016 18:29+1Относительно задания с
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
(последние два — только и исключительно в виде макросов либо каких?либо выражений, получающих значение этих макросов, а не конкретных значений) — «я не знаю».
deviant_9
13.11.2016 09:19Третий вопрос полностью о темных углах. Начиная с того, что ни переполнения integer
Переполнение возникнет лишь если результат умножения не помещается в int.
Преобразование между целочисленными типами данных (в данном случае от int к char) — немного другая статья. Если результирующий тип — беззнаковый, то гарантируется модулярная арифметика по модулю 2^<число бит>; в противном случае результат будет implementation-defined (в поздних стандартах на этот случай появилась фраза "...or an implementation-defined signal is raised", но это всё равно не undefined behavior).
Более того, размер типа char в битах не определен. Существовали платформы где он был по 6 бит (помните триграфы?) и существуют платформы где все пять целочисленных типов по 32 бита.
Стандарт требует, чтобы CHAR_BIT был не меньше 8 бит.
TheCalligrapher
13.11.2016 18:33В первую очередь стоит заметить, что язык С уже давно не поддерживает объявлений функций без явного указания типа возвращаемого значения. Правило неявного int было отменено ещев 1999 году. Поэтому использованное в примерах 'main ()' без явного 'int' — это уже не С.
iCpu
14.11.2016 06:13https://ideone.com/mCJZ2r
TheCalligrapher
14.11.2016 06:20Напоминаю, что мы говорим про язык С.
Во-первых, язык С для компилятора GCC, который скрывается за ideone, начинается с флагов `-std=...` и `-pedantic-errors`. Без этих флагов компилятор GCC является компилятором развеселого студенческого языка для пивных вечеринок, никакого отношения к С не имеющего.
Во-вторых, фронтэнд ideone известен грубой фильтрацией диагностических сообщений, по каковой причине для демонстрации свойств языка всерьез рассматриваться не может.
Научитесь пользоваться хотя бы coliru
http://coliru.stacked-crooked.com/a/aef58ce98b518d02
Существуют и другие уважаемые онлайновые фронтэнды. Но не ideone.iCpu
14.11.2016 06:23+1> Научитесь пользоваться хотя бы coliru
Этот ядовитый комментарий был излишним, с остальным вынужден согласиться.
Siemargl
14.11.2016 11:00Новее -std=c90 модификация языка это уже избыточные финтифлюшки, а в C90 еще можно не объявлять тип.
Вот С++ изначально ответственнее относится к контролю типов.TheCalligrapher
14.11.2016 11:19Во-первых, ни в коем случае. Язык С — это язык не ниже С99. Формально С11 — нынешний стандарт, но его можно назвать достаточно минорной модификацией С99 (ака "финтифлюшками"). Но С99 обсуждению не подлежит.
Во-вторых, как уже говорил выше, контроль типов всегда был строго идентичным в С и С++, за исключением возможности неявной конверсии из
void *
в С. Да, современный С++ усилил контроль за конверсиями (за счет запрета narrowing conversions в ряде контекстов), но это ведь не то, что вы имели в виду, правда?
TheCalligrapher
13.11.2016 19:41+1"Существовали платформы где он был по 6 бит (помните триграфы?)..." — это замечание, конечно, совершенно "мимо кассы". Особенности символьного набора (execution character set) конкретной платформы формально влияют на размет типа 'char' только тем, что обязаны в него помещаться. Не более того.
Даже в стариннейшем "C Reference Manual" тип 'char' уже постулировался, как знаковый 8-битный тип в формате 2's-complement. А дальше его спецификация становилась лишь еще более абстрактной и свободной.
Sun-ami
14.11.2016 12:05-1C и 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, а в случае необходимости — перейти к более расширенному подмножеству.Sun-ami
14.11.2016 12:29Проблемы с форматированием:
— вместо int, short, long и long long ввести int<«N»>где «N»-гарантированное минимальное число бит в платформозависимом типе;
— не упакованная структура с сохранением порядка полей:
struct<ordered>
— структура, в которой компилятор может изменять порядок полей:
struct<auto>
deviant_9
14.11.2016 22:34+1вместо 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*.Sun-ami
15.11.2016 12:58>гарантии там и так есть
Это, конечно, хорошо, но половинчато и не универсально. В некоторых процессорах (DSP) промежуточные результаты вычислений хранятся в регистрах размером 18, 20, 24 или 48 бит. В компиляторах для них есть соответствующие типы, но с переносимостью кода есть проблемы. Для 8-битных микроконтроллеров были бы полезны типы с разрядностью 24 и 40 бит. А кое-где уже поддерживается 128-разрядная архитектура. Вместо того чтобы каждый раз менять стандарт, лучше 1 раз ввести универсальную конструкцию, её поддержка компиляторами потребует не слишком много усилий.
Sun-ami
15.11.2016 19:34>>Вместо int8_t, int16_t и т.д. — ввести похожую конструкцию, но с чётко фиксированным размером в битах
>У них-то как раз фиксированные размеры
Я имел в виду конструкцию, похожую на предложенную мной выше int<«N»>, с произвольной фиксированной разрядностью. Например int<16f>, int<18f>. А за int_fast_t спасибо, не знал, буду использовать.
pengyou
Жаль, что в ИТ-мире нет гравитации или массовых вымираний, в первом случае технологии бы падали и разбивались/тонули/улетали в космос (подальше, к Конской Голове, например), а во-втором случае технологии бы вымирали вообще насовсем, как трилобиты, например. Жаль, что Си ещё жив.
CodeRush
Для системного программирования у C маловато альтернатив, зато имеется огромный багаж существующего кода. Непонятно, на что его можно заменить сейчас там, где указатели реальны, а из инструментов отладки только UART и доброе слово?
На ум приходят Ada и Rust, но первый язык так и набрал популярности (хотя он во многих аспектах намного лучше подходит для системного программирования), а второй пока еще довольно молод, но я искренне за него болею.
Можно, конечно, попробовать «причесать» уже имеющийся С (Cyclone, Checked C, etc.) или ограничиться небольшим «безопасным» подмножеством языка (JPL Coding Rules, MISRA C, etc.), но это все мало кто может себе позволить, т.к. программировать с такими ограничениями умеют меньшее количество специалистов, и сама разработка в итоге становится дороже (иногда существенно) и медленее (иногда на порядок).
rstepanov
Иногда из инструментов отладки только 1 светодиод. А иногда нет даже его…
vk2
… но есть логический анализатор и осциллограф…
stychos
Это слишком дорого, надо на язык щупать.
michael_vostrikov
Писал программки для защищенного режима, в процессе работы отключалась видеокарта, выводил адрес инструкции с ошибкой спикером — два коротких бипа «1», один длинный «0».
VioletGiraffe
C++ же.
Я, наверное, ни за какие деньги не взялся бы писать на чистом С. Это хороший язык, но слишком простой. Я не доверяю софту, написанному на С, потому что понимаю, как сложно писать на этом языке большие системы без ошибок. Это тот же ассемблер, только кросс-платформенный (ну, по большей части).
lorc
На самом деле C++ добавляет собственных тараканов к тем, что есть в C. Конечно, если писать на С++ правильно, то код будет намного безопасней. Но если писать на С правильно, то опять же можно бороться со сложностью. Есть куча прекрасных примеров: ядро linux, git, nginx, etc
Ruckus
В ядре linux, на сколько знаю, используют «С с классами», у гита (на зеркале на гитхабе) меньше половины кода на сях, что о многом по моему говорит, nginx по первому взгляду (не изучал особо) и правда на чистых сях, но это скорей исключение и такой проект, как мне кажется, гораздо трудней поддерживать, чем любой ООП. Хотя не исключаю, что у меня ООП головного мозга.
aso
Но как, сэр?
Это же чисто эппловская примочка, да ещё и какая-то высокоуровневая…
lorc
Элементарно, сэр. Структура, которая хранит указатели на функции — и вот мы получаем абстрактный объект. Заполняем указатели — и вот у нас экземпляр конкретного класса. Заменяем некоторые указатели на свои, немного магии с container_of и вот у нас наследование.
Ядро Linux объектно-ориентировано, хоть и написано на C. Такие дела.
aso
«Си с классами» — «C with classes» — название первой версии С++, написанной Страусом трупом.
Соотв., Ваше утверждение выглядит странно.
Я уже не говорю о том, что «структура, хранящая указатели на функции» — не обязательно эмулирует классы.
lorc
Извините, но «Си с классами» сказал не я. Я просто рассказал как ядро устроенно внутри.
Не обязательно. А ещё для реализации ООП не обязательно иметь ключевое слово class в языке. Более того, язык вообще не может знать про классы. И при этом на нём можно писать объектно-ориентированный код.
Ruckus
Я имел ввиду совсем не то. Прошу прощения, не углублялся в историю и «трупов» не изучал, потому любую имитацию ООП на чистом С называл «С с классами». Больше не буду, еще раз простите.
stychos
https://www.cs.rit.edu/~ats/books/ooc.pdf
Ruckus
Вы говорите про ObjC, а это совсем другой язык. И еще вам стоит знать, что «компилятор Objective-C входит в GCC и доступен на большинстве основных платформ». То, что это был основной язык программирования (коим возможно остается и сейчас) для устройств фирмы Apple никак не делает его «эппловским».
Ruckus
Да что я опять не так сказал? Вместо минуса лучше бы написали где я не прав, в следующий раз хоть знать буду.
lorc
Зависит от задач, как всегда. Если у нас что-то гетерогенное, с базами данных, REST-запросами, гибким логгированием и GUI до кучи, но при этом хочется скорости — надо брать C++ иначе можно рехнуться.
Если у нас есть конкретное очерченная задача (git) — хватит и C. Если нет rich runtime (linux) — опять С. Если мы максимально экономим ресурсы (nginx) — снова С.
Но при этом почти весь хороший код на C всё равно оперирует объектами. Да, там часто проблемы с полиморфизмом и наследованием. Но абстракция данных и инкапсуляция цветет пышным цветом. Достаточно посмотреть API любой сишной библиотеки (кроме libc, естественно).
0xd34df00d
Совершенно неочевидно, почему, кстати.
deviant_9
В ядре активно используются gcc-шные расширения, но «объектно-ориентированные» фишки реализуются вполне себе штатными средствами чистого Си. Никакого особого «Си с классами» там нет:)
Ruckus
Прошу прощения. Я не особо знаю всякие экзотические вещи, такие как названы выше. Под «С с классами» я имел ввиду реализацию ООП на чистых Сях, иногда с использованием GTK и прочих, по моему мнению, извращений. Да, экономия ресурсов и «переносимость», но это неудобно и во многих случаях напоминает как раз написание своего С++, но почему не взять готовый никто отвечать не хочет.
lieff
Я не противник С++, но в ветке видел несколько ответов почему в некоторых задачах не взять готовый С++ вместо С:
Ruckus
Вы правда смотрите на объем apk и вообще скомпилированного кода?
Может в этом и есть какой-то смысл, но не в ущерб читаемости, удобству и качеству. По крайней мере на тех платформах, где оперируют уже давно гигабайтами.
В ближайший месяц мы выпускаем приложение на Android, в котором часть кода написано на C++ (NDK) с использованием c++_shared (LLVM). Объем библиотек под все платформы вышел на 40-45Мб (7 архитектур), то есть в среднем 6.5Мб на архитектуру. Все приложение весит около 17Мб (apk). Для сравнения то же приложение на iOS весит 15Мб (С и ObjC). Вот уж не вижу смысла бороться ради пары мегабайт.
В плане оперативной памяти все аналогично.
lieff
А я и не говорю, что всегда это надо, я сам предпочитаю C++, потому и говорю — в некоторых задачах. А смотреть на размер — да, бывало, было дело сократили размер so до 1.5мб\архитектура (3мб apk, против изначальных ~40мб). А вот приложения по 17мб я и сам не люблю, с нашим то мобильным инетом, а как дела в Китае? А в Индии?
Другое дело что, если это игра, то ресурсы и\или сам код могут занять много больше рантайма и смысла действительно не будет. Но это не значит, что таких задач не бывает, это мы еще не коснулись микроконтроллеров, демосцены, системного\ядерного кода, специфичных кейсов типа портирования c winelib.
CodeRush
Непонятно, в чем преимущество C++ в тех нишах, где позиции С по прежнему сильны: embedded, firmware, kernel core, вот это все. Там не нужны исключения, полиморфизм и RTTI, там нет стандартной библиотеки, там мало толку от шаблонов, там часто вообще нет кучи, т.е. единственный доступный operator new — размещающий, и т.п.
Для «нормального» C++ нужен гораздо более жирный рантайм, а специалистов, умеющих писать на «обрезаном» С++ — намного меньше, чем специалистов по С, т.к. такому «ограниченному» использованию С++ почти нигде не обучают.
DeadKnight
Основное преимущество C++ перед C в перечисленных нишах — метапрограммирование.
VioletGiraffe
Наверное, если система очень небольшая и влазит в считаные килобайты, от С++ пользы нет, один вред.Я под такие системы использовал компилятор С++, но писал, по сути, на С. А вот под STM32 d проекте на 5000 строк кода я использовал и шаблоны, и полиморфизм. Можно ли без них? Конечно, можно. Хочу ли я не пользоваться этими средствами? Нет, не хочу, они позволяют мне писать понятный, легко расширяемый и корректный код.
aso
Я как-то делал библиотечку, которая напрямую компилировалась в простые выражения присваивания или чтения битов ввода-вывода.
Ну, типа — там несколько связанных битов, конфигурирующих ввод-вывод и сам I/O pin.
Разумеется, это было несколько инлайновых шаблонов с наследованием — выглядело, в общем, неплохо.
А биты адресовать невозможно (т.е.Си с ними вообще с ума сходил).
Ну и конструкторы-деструкторы — штука няшная.
Akon32
Битовые поля структур не могли бы помочь?
aso
Битовые поля структур точно так же не адресуемы.
Ну т.е. сделать на них тоже можно было — но вряд-ли получилось бы эффективнее.
Плюс необходимость их самому определять «ручками» — а биты определялсь при конфигурировании проекта и выборе контроллера.
jcmvbkbc
std::vector<bool> смотрит на это заявление с недоумением.
aso
И хде там биты?
jcmvbkbc
Внутри. Это битовый вектор.
aso
А.
Значиццо у нас есть микроконтроллер, для которого есть Си/С++ со специфическим расширением — типом _bit.
Некоторые биты имеют специфическую функциональность — ну там ввод/вывод, настрока пина на вывод, открытый сток/пуш-пул…
И поверх всей этой радости предлагается присобачивать std::vector, который вообще «Не обязательно хранит свои данные в одном непрерывном куске памяти.» (© cppreference)?
Обёртку я тоже нопейсать могу — что я, собственно, там и сделал.
jcmvbkbc
Нет, std::vector<boot> приведён как широко известный пример техники адресации битов.
И, да, правила.
aso
Это эмуляция битовой адресации, а не.
И да, в треде утверждалось, что «С++ нафик не нужен в эмбеде со своими шаблонами, наследованием и т.д.».
Например, вот tyt.
Ваш тезис ничего не добавляет к моему оппонированию этому.
haqreu
Попробуйте сделать std::swap на двух элементах std::vector<bool>.
jcmvbkbc
Попробовал, сделал:
Учитывая то, что исключений здесь быть не может, в чём подвох?
haqreu
А скажите, зачем вы переопределили std::swap?
jcmvbkbc
Готовый std::swap ожидает, что ссылочный тип оканчивается на '&', что, конечно же, не всегда правда. Это же, вроде, известная ловушка шаблонных функций?
TrueBers
В современных компиляторах, поддерживающих C++11 и выше, swap давно уже как перегружен для
и всё отлично работает.jcmvbkbc
Да, верно. Перегрузка нужна для С++98. Тем более непонятно, в чём подвох.
Antervis
а есть и недостатки. Например, подключение библиотек STL, и программа разрастается до таких размеров, что не влазит в контроллер. В итоге придется писать на подмножестве, одинаковом для с и с++.
a-tk
Метапрограммирование, строгая типизация
Нередко более эффективный код на шаблонах, чем код в лоб, правда ценой раздутия кода. То же самое, конечно, можно сделать на сях, но дольше и с большей вероятностью ошибки.
TheCalligrapher
Язык С практически ничем не отличается от языка С++ в области строгости типизации. Единственное отличие, которое навскидку приходит в голову — это возможность неявной конверсии
void *
в другие объектные типы указателей в языке С. Все.Так о какой строгой типизации вы ведете речь?
Antervis
например, неявный каст const T* к T*
TheCalligrapher
Это откуда? В языке С никогда не было неявного приведения `const T*` к `T*`. Разумеется с того момента, комитет X3J11 ввел в язык С `const`.
deviant_9
Ещё перечисления (преобразующиеся к целочисленным типам, но не обратно), особенно появившиеся в 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* (указатель на базовый класс всех виджетов, а не на конкретный класс виджета), и т. д.
TheCalligrapher
Да, перечисления — хороший пример, в дополнение к
void *
. Еще что нибудь? (Явные касты же — не по теме. Явные касты — это средства обхода контроля типов.)Заявления о более либеральном контроле типов в С как правило базируются на "пионэрских" верованиях, что С якобы разрешает такие вещи, как
int *p = 123
или игнорирование const-корректности (см. яркий образчик такого заявления выше). В качестве доказательства приводится тот факт, что чей-то уютненький компилерчик выдает на это "всего лишь warning".Понятно, что к реальности такие заявления никакого отношения не имеют.
deviant_9
Так обходить по-разному можно. (T)v значит вообще практически все проверки выключить, в то время как специализированные касты отключают лишь некоторые проверки (например, static_cast и reinterpret_cast не позволят ненароком сбросить const).
TheCalligrapher
Это прекрасно. Базовые факты о поведении С++-style casts тут все хорошо известны.
Как это относится к теме более строго контроля типов? Контроль типов в языке — это именно контроль неявных преобразований. Явные преобразования (касты) к этой теме не относятся.
В любом случае, "созвездие" С++-style casts позволяет вам сделать все, что угодно (не говоря уже о том, что C-style cast никто в С++ не запрещал).
deviant_9
Приведу пример. Допустим, есть у нас некая функция, которая по каким-то причинам (крайне устаревший код, некомпетентность автора, потенциальное наличие заведомо нереализуемых в данной ситуации особых вариантов поведения и т. д.) принимает параметр типа char*, а не const char*, но аргумент свой, как мы точно знаем, не модифицирует: А нам нужно вызвать её из другой функции, передав аргумент типа const char*. Приходится использовать const_cast:
Если человек на фоне недосыпа вместо этого случайно написал то компилятор ему об этом скажет: const_cast<char*>(x) — невалидная конструкция.
Но в языке Си мы вынуждены использовать cast notation (ничего другого нет) — там аналогичная функция с точки зрения компилятора будет полностью валидной.
В одном случае контроль типов сработал, в другом — нет.
В любом случае ремни безопасности можно не пристёгивать.
Если у человека есть _цель_ отстрелить себе ногу, то C++-style casts ему действительно ничем не помогут. Но контроль типов — это немного про другое.
Siemargl
Собственно, warning's не имеют никакого отношения к корректности языковых конструкций (соответствия синтаксису).
Это всего лишь дружеская необязательная помощь компилятора.
deviant_9
Таки имеют.
5.1.1.3 Diagnostics
В сноске:
Warning — одна из реализаций «diagnostic message». А успешная компиляция программы допускается даже если последняя некорректна.
0xd34df00d
Но из этого не следует, что каждый warning приходится на одно такое требование стандарта.
TheCalligrapher
Нет. Это популярное наивное заблуждение.
Во-первых, это было бы справедливо только в отношении компиляторов, которые строго и аккуратно разделяют warnings и errors про указанному вами принципу. Таких компиляторов С просто нет.
Например, при компиляции в GCC в умолчательной конфигурации огромное число warnings — это именно грубейше некорректные языковые конструкции, которые GCC просто так решил отрапортовать как warnings (ради совместимости с каким-нибудь старинным кодом).
Попыткой достижения строгого деления на warnings и errors в GCC является флаг
-pedantic-errors
, но и он еще не идеален.Во-вторых, формально придраться к GCC тут нельзя, ибо в С нет понятия warnings и errors. Есть только понятие diagniostic message. Задача компилятора — сказать хоть что-то. А уж разбираться затем в формальной корректности конструкции, листая стандарт языка — это ваша задача.
Siemargl
Т.е. оба примера допустимы в С, но недопустимы в С++
TheCalligrapher
Неверно. И
main()
иint *p = 123;
являются грубыми ошибками ("contraint violation") в одинаковой степени и в С, и в С++. Ваш компилятор С выдал требуемые диагностические сообщения, сообщив вам тем самым, что ваша программа не является программой на С и ее поведение не определено языком С. Точка.Однако видя, что вы пока не умеете "на глаз" отличать "безобидные" диагностические сообщения от серьезных ошибок, я бы посоветовал вам почитать документацию своего компилятора и попросить у него помощи в этом. В частности, в GCC это делается флагом
-pedantic-errors
. Возьмите в привычку пользоваться этим флагом всегдаSiemargl
Я прекрасно понимаю как оно скомпилируется и как работает.
Например вижу ошибку выхода за границу массива в коде выше, о которой НЕТ варнинга.
И меня такое деление устраивает — error как принципиально не компилируемый код, warning- обратите внимание.
Ваши привычки не надо пытаться навязывать, даже если они могут быть полезными. Они могут оказаться ограниченными.
Например, вчера у меня после смены версии gcc, в newlib (это такая версия libc, кто не в курсе), появились варнинги (типа char применяется как индекс массива, ай-яй).
И что вы предложите мне выкинуть — платформу, gcc, newlib или %^&%##-pedantic-errors?
TheCalligrapher
Уважаемый, во-первых, то, что вам тут пытаются объяснить, это тот простой факт, что
main()
иint *p = 123;
в языке С формально являются именно принципиально не компилируемым кодом. То, что ваш компилятор этот код проглатывает — это замечательно, но к языку С никакого отношения не имеет. И, соответственно, не имеет никакого отношения к теме этой дискуссии. С таким же успехом вы могли бы рассказывать нам, что там у вас компилируется в компиляторе Фортрана.Никто вас при этом не призывает прекратить писать такой код — пишите на здоровье что вам заблагорассудится. Но не удивляйтесь потом, что на ваши попытки выдать ваш код за код на С будут смотреть с откровенным нескрываемым недоумением.
Во-вторых, упоминая
-pedantic-errors
я вам не пытаюсь навязывать никакие привычки. Никто вас не заставляет использовать-pedantic-errors
повседневно, тем более, что вы к этому явно еще не готовы. Вы сделали стандартный, уже всем надоевший ляп: попытались доказывать какие-то странные верования о языке С на основе разухабистого поведения неумело сконфигурированного (или вообще не сконфигурированного) компилятора. Но вот перед тем как презентовать свои верования на публику — не помешало бы проверить их с помощью этого флага. Это позволит вам избежать попадания в такие постыдные ситуации в будущем.В-третьих, если я что вам и навязываю тут — так это сам язык С. Не больше, не меньше. За одиннадцать лет в JTC1/SC22/WG14 у меня выработалась такая привычка.
P.S. К чему здесь вдруг ваши разглагольствования на тему "что вы предложите мне выкинуть" — мне не ясно. Никто вам тут ничего выкидывать пока не предлагал.
Siemargl
Я не знаю, в каком выдуманном мире вы живете, или как трактуете мат.часть С, но этот код прекрасно компилируется и Clang, и MSVC, и самое интересное работает так, как мне и требуется.
Не буду уточнять версии, т.к.больше чем уверен что этот код соберется любым из десятка имеющихся у меня С-компиляторов.
Это глупая дискуссия между теоретиком и практиком.
TheCalligrapher
О! Уж совсем детские наезды пошли!
Никакого спора тут нет и быть не может. Все эти вопросы уже давным-давно имеют четкие и однозначные ответы. Вы просто по неопытности своей в первый раз эти ответы слышите. Поэтому я даю их вам под запись, а вы — тщательно изучаете и зазубриваете наизусть. Никакого спора.
Что касается вашей феерически "альтернативной" интерпретации работы этих компиляторов — здесь это никому не интересно. Здесь речь идет именно и только о языке С. И все эти компиляторы немедленно откажутся трактовать ваш ошибочный код, как код на С. В противном случае они бы под общий хохот были бы вышвырнуты с рынка смачным пинком под зад.
Что же касается вашей "практики" — это не практика, заблудший вы наш, это что-то вроде "диссертации Василия Ивановича", в которой он, согласно известному анекдоту, оторвав таракану ноги, пришел к выводу, что "безногий таракан не слышит". Или, если угодно, это — изучение творчества группы Битлз по напевам Рабиновича, согласно другому известному анекдоту.
Если вы намерены серьезно изучать и использовать язык С, всю эту вашу "практику" вам придется тщательно стереть из головы — она бесполезна и будет вам только мешать. Придется накапливать с нуля совсем новую практику. А уж когда вы напрактикуетесь хотя бы с половину моего — вот тогда и понимание теории подтянется. Я знаю, ибо сам когда-то таким был.
Siemargl
Как показано ниже со ссылками на первоисточники, стандарта вы тоже не знаете, как и практики.
Потому оставьте весь поучающий пафос себе.
TheCalligrapher
Молодой человек, вы со своими "ссылками не первоисточники" смачно сели в лужу именно так, как садится в нее практически каждый новичок, неправильно понявший 6.3.2.3. Вы у меня уже, наверное, тысячный...
Учитесь не просто читать, а еще и понимать прочитанное. Я вам там все подробно разъяснил, под вашей "ссылкой не первоисточник".
Поэтому еще раз, у вас появляется редчайшая и уникальнейшая возможность поговорить с "отцами", думайте не о каком-то воображаемом "пафосе", а том, чтобы записывать и тщательнейше зубрить каждое услышанное слово. Другой такой возможности вам может не предоставиться.
deviant_9
То есть вы ещё не перешли на utf-8?
Siemargl
Нет. И если верить стандарту С11, надо было писать u8«Нет»
deviant_9
В цивилизованном мире UTF-8 — кодировка по умолчанию. Так что если вы сохранили исходник в UTF-8, то и результирующая строка в норме должна быть в UTF-8, даже в отсутствие префикса u8 (как минимум, у меня под линуксом в gcc дела обстоят именно так). Это не требуется стандартом (source character set и execution character set, в принципе, могут быть разными), но и не противоречит ему.
Чтобы (достоверно) увидеть ошибку выхода за границы массива в вашем примере, надо знать компилятор и кодировку исходника. Без этой информации отсутствие выхода за границы более вероятно.
Siemargl
Полагаться на кодировку исходников — чистой воды непереносимость.
grossws
ASCII является валидным UTF-8, если что.
Siemargl
Собственно, поскольку грубую ошибку вы не заметили, а придираетесь к пуговицам, возвращаю вам «что вы пока не умеете „на глаз“ отличать „безобидные“ диагностические сообщения от серьезных ошибок, я бы посоветовал вам» =)
Поведение там определено языком С, хотя и непереносимо (только для второго ворнинга).
Кстати именно буквоедство свойственно многим новичкам — это к посту ниже по некоторых индивидуумов.
TheCalligrapher
Нет.
Программа некорректна — содержит constraint violations, т.е. синтаксические или семантические ошибки. Такая программа именно некомпилируема (!) как программа на языке С.
Компиляторам разрешается компилировать такой код с той оговоркой, что поведение такой программы языком С не определено, ибо программой на С такой код не является.
И для этого достаточно только
main()
, не говоря уже оint *p = 123;
. Дальшеmain()
уже можно не смотреть.И не надо пытаться вертеть хвостом с вашим выходом за пределы массива. Он тут не имеет никакого значения. Кстати, выход за пределы массива не является constraint violation, т.е. компиляторы имеют полное право компилировать ваш выход за пределы даже и не пискнув. Диагностики времени компиляции тут не требуется.
Siemargl
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, но иногда при работе с железом так и нужно делать. Не запрещено.
TheCalligrapher
Язык С — это язык, описываемый действующей на данный момент спецификацией языка. Язык С описывается спецификацией ISO/IEC 9899:2011, известной в популярной культуре, как С11.
Спецификация С89/90 описывает язык С89/90, а не язык С. Точно также как книга K&R С, описывает язык K&R С, а не язык С.
Создание указателя путем попытки неявного преобразования из
int
— это с точки зрения языка С не UB, а некомпилируемый код. При работе же с железом, когда это нужно, делается так:int *p = (int *) 123;
, но ни в коем случае неint *p = 123;
. Язык безусловно требует явного каста для выполнения такого преобразования.Siemargl
Вот уж нет, приведение указателей на разные типы и конверсия из int это как раз даже не UB, я преувеличил — implementation defined, так написано в 6.3.2.3 пункт 5 стандарта.
И следуя вашей логике, программы до С11, не являются программами на С.
Вот это уже называется вертеть хвостом, пытаясь апеллировать к последнему стандарту =)
Собственно, статья права — никто не знает С.
TheCalligrapher
Осспади… 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.5.4 Cast operators специально добавлено
А дальше вам прямая дорога в описание семантики инициализации, которая в свою очередь отошлет вас к описанию семантику оператора присваивания (а это тот самый 6.5.16 Assignment operators), где ни в коем случае не допускается комбинации из указателя и целого числа в присваивании, за исключением случая, когда справа стоит null pointer constant.
Бывает, как видите, еще хуже. Когда человек не только не знает С, а не в состоянии даже толком разобраться в выложенным перед ним описании языка.
Siemargl
Ах, дело лишь в том, что лично вы хотели бы там видеть явное приведение типа, а компиляторы вас не слушают?!
Ну извините, мир с розовыми понями за углом.
Мне с практической точки зрения важна только принципиальная возможность такого приведения в языке, поскольку языки с запретом подобного поведения адски неудобны.
И ругнется компилятор варнингом или нет — не суть важно, программа не станет от этого принципиально неверной.
За сим откланиваюсь.
TheCalligrapher
Не слушают? Да что вы говорите? Опять девичьи мечты?
В полном соответствии с моими требованиями (то есть требованиями спецификации языка), компиляторы четко и ясно указывают на ошибочный код при помощи диагностического сообщения. Это вы лично их не слушаете, тупо игнорируя эти диагностические сообщения :)
И, я вижу, опять начались томные рассказы про то, что «вам нужно»… Я же, как я уже не раз говорил выше, веду речь именно и только о языке С, а не том, что мне или кому-то еще лично нужно.
Ну что ж — и наше вам с кисточкой.
deviant_9
Как минимум, gcc (не такой уж и уютненький, вполне себе серьёзный, по строгой поддержке стандарта в том числе) по умолчанию ведёт себя именно так. Можно и const сбрасывать, и вообще совершенно разные объектные типы неявно приводить друг к другу — будет только предупреждение, а при компиляции C++-программ — ошибка.
Diagnostic message есть — формально требование Стандарта со стороны компилятора соблюдено.
TheCalligrapher
С тем что формально требование стандарта соблюдено никто не спорит. В данной подветке речь идет не об этом, а о том о том, что делать выводы о корректности программы на основе того, как некий компилятор делит свои диагностические сообщения на errors и warnings — занятие совершенно бессмысленное. Тем не менее некоторые индивидуумы этого не понимают.
Это деление в случае GCC — не более чем "уютненькая" фантазия авторов компилятора, обилием каковых фантазий компилятор GCC широко знаменит. И подрастающее студенчество, формальных документов не читавшее, из-за этой "уютности" страдает заметно, как показывают в том числе и комментарии в этой ветке.
Когда-то нам стоило огромных усилий пробить разработку в направлении "педантичного" деления на ошибки и предупреждения в GCC (пресловутый
-pedantic-errors
), Почему это направление разработки вызвало такое сопротивление в GNU — мне до сих пор не ясно.akastargazer
Авторы С подтверждают — язык С был придуман для того, чтобы Ричи и Кернигану не надо было напрягаться программированием на ассемблере. Цитировать не буду, но этот момент обозначен в их книге.
TheCalligrapher
Надо заметить, что Брайан Керниган не имеет никакого отношения к разработке языка С вообще, как он сам не раз заявлял. Как активный участник разработки языка В (предшественника С) он участвовал в подготовке книги, но не более того.
TheCalligrapher
Это во-первых. А во-вторых, на тот язык, который Денис Ритчи разработал «для замены ассемблера», можно взглянуть в известном документе самого Дениса Ритчи: «C Reference Manual» (CRM)
http://www.math.utah.edu/computing/compilers/c/Ritchie-CReferenceManual.pdf
В этом языке действительно чувствуется сильное влияние ассемблера. Однако все это было выкинуто из языка очень быстро: CRM C очень сильно отличается от K&R C, не говоря уже о ANSI C, не говоря уже о современном С.
polifill
Обеспечение переносимости при разработке с C++ — отдельная головная боль.
То есть вы сосредоточены не только на решении задачи, вам нужно еще и с компилятором побороться.
С++ не так просто скомпилировать на разных версиях операционной системы даже при использовании одного и того же компилятора (не говоря уже про разные компиляторы и разные платформы и разные стандарты С++).
Например,
https://github.com/cocaine/cocaine-core
Это одна из базовых технологий Яндекса, сделанная не самыми глупыми программистами.
VioletGiraffe
Я разрабатываю кросс-платформенные приложения под Винду, Линукс, Андрои, иОС и Мак. Ваше заявление, мягко говоря, не соответствует действительности. Яндекс где-то накосячил.
polifill
Это можно — кто ж спорит.
Но это требует серьезных ДОПОЛНИТЕЛЬНЫХ затрат или просто СПЕЦИАЛЬНЫХ знаний.
Я утверждаю, что львинная часть Сипласпласовцев таковыми не очень то и стремится овладеть сосредоточившись на своей узкой задаче.
Ну не должен язык, претендующий на базовый, быть таким ненадежно-непредсказуемым.
Чтобы было понятно о чем речь и что проблема решаема в принципе — сравните, сколь небольшими усилиями делается переносимая программа на Python или Go.
С С++ не сравнить — это просто небо и земля.
В Python и Go если разработчику и нужно с чем то бороться при этом — то с отдельными аспектами не POSIX API (Windows). Что как раз ожидаемо и логично.
С С++ дополнительно приходится бороться с различиями в компиляторах и библиотеках — даже в пределах ОДНОЙ платформы.
P.S.:
Яндекс приведен как контора, которая может позволить себе разработчиков уровня выше среднего. И если даже они косячат со своими базовыми вещами (эта штука, что я привел — занимается запуском и мониторингом контейнеров с другим ПО в кластере Яндекса) — то что же ожидать от основной массы?
Не должен быть язык таким. Это плохое свойство. Существенный недостаток. Никак он не может претендовать на системный, базовый.
VioletGiraffe
Я не помню, когда последний раз за 6 лет кросс-платформенной разработки «боролся с различиями в компиляторах и библиотеках». ЧЯДНТ?
Но я уделяю внимание использованию стандартного С++ и всячески избегаю любых платформозависимых расширений. "-pedantic-errors", /W4, периодический статический анализ и всё такое.
polifill
Возьмите и откопилируйте программу по ссылке выше.
Да, там кое-где код написано не универсально. Но дело не только в этом.
Дело не только в СТАНДАРТНОМ С++. Там еще куча ЗАВИСИМОСТЕЙ.
Все от вас зависит только в ПРОСТЕЙШЕМ ПО, которое вы пишете сами.
Как только начинаете использовать внешние вещи (библиотеки) — количество сюрпризов увеличивается.
Вплоть до багов в такой казалось бы базово-универсальной вещи как Boost, не говоря уже о более специфичных библиотеках…
VioletGiraffe
Бибилиотек у меня около 15, от exiv2 до libusb и libftdi. Естественно, используем только кросс-платформенные библиотеки.
Буст не использую, он слишком большой и сложный в интеграции.
polifill
Алогичность детектед:
Средний программист С++ (коих подавляющее большинство) понятия не имеет как писать 100% переносимый код.
Вы же приводите в пример просто «историю успеха».
Никто не спорит, что при желании на С++ можно писать переносимо.
Совсем другой вопрос — а какими это достигается усилиями, по сравнению с более естественными в этом отношении системами программирования. Ну например, Python и Go и Java…
VioletGiraffe
Я работаю в маленькой фирме и ничего не знаю про среднего программиста. Я знаю, что пришёл работать после ВУЗа с нулевым опытом, методом тыка стал разбираться с портированием уже имевшегося когда на другие платформы, и быстро научился любить и уважать стандарт С++. А потом почти так же быстро научил любить стандарт сотрудников и начальника. В том числе и с помощью -pedantic-errors.
Как только начинаешь писать под больше, чем один компилятор и/или одну платформу — очень быстро дисциплинируешься и всё встаёт на свои места.
А ваши слова про подавляющее большинство — пустой звук, не подкреплённый никакими доказательствами.
На Go и Питоне уже можно писать GUI, или по-прежнему как всегда? Питон вообще не рассматриваю как что-то большее, чем средство написания скриптов для автоматизации каких-то относительно небольших процессов. Java в целом хороша, но жирный рантайм, кривой UI и производительность «как повезёт». Это моё ИМХО, your mileage may vary.
polifill
Вы вновь повторяете алогичность.
Ваш частный случай — это всего лишь ваш частный случай.
Факты таковы, что большая часть людей даже ни разу не стремятся профессионально рости. Это касается любой профессии, а не только профессии программиста.
И, к сожалению, создатели третьих библиотек не всегда уважают стандарт и не всегда проверяют кросс-платформенность.
Я уже привел пример Яндекса, который может себе позволить нанять специалиста заметно выше среднего (и дело не только в деньгах, они могут позволить себе нанять специалиста даже потратив меньше, потому как все прекрасно понимают, что после Яндекса — все пути тебе открыты).
И тем не менее — видим огромные косяки. Там даже не так просто скомпилировать под Ubuntu другой версии…
А ведь это не какой то локальный продукт — их Cocaine базис облака Яндекс и распространяется свободно для любого желающего.
Для того, чтобы убедиться, что большинство С++ создают продукты такого же качества — достаточно побольше взять открытых проектов и попытаться их скомпилировать (даже не запустить!!! ) на различных системах.
Если я скажу что я делал это — и все очень часто плохо — это же не будет для вас доказательством? Вы можете это понять только самостоятельно.
Проблема с GUI решаема.
Совсем не обязательно создавать монолит. GUI (если у вас не нечто требовательное до 3D, к примеру) — вполне безболезненно может существовать отдельно от вычислительной части.
И, очень интересно, а что вы называете кросс-платформенностью своей системы? Где именно возможна ее работа, на каких ОС?
VioletGiraffe
1. Факты таковы, что вы бросаетесь общими незначащими фразами, не подкреплёнными доказательствами, выдавая их за факты. Пример: «большая часть людей… не стремятся профессионально расти».
2. Вы аппелируете к авторитету Яндекса, но для меня это не авторитет.
3. В репозитории есть README. Там принято писать список поддерживаемых платформ и компиляторов. Ожидать беспроблемной сборки на других системах не стоит. Есть спецификация, там написано, что система гарантирует. Всё остальное вам никто не обещал, так чего вы ожидаете? Вы, наверное, из той категории людей, которые ставят приложению в Google Play негативные отзывы за то, что приложение не умеет ещё и кофе варить.
О чём мы вообще спорим? На С++ можно писать непортируемый код? Да, можно. И на Java можно. Банально, вызвали WinAPI функцию — всё, ваш код стал непортируемым. На С++ вызывать сторонние компоненты проще, и для меня это преимущество, а не недостаток. Правильное использование фич всегда на совести програмиста, а не языка.
Зато писать портируемый код на современном С++ намного проще, чем на С (кроме самых embedded платформ, наверное).
4. GUI на основе другой технологии — лишняя головная боль. Всякие прослойки-адаптеры., через которые надо правильно протащить каждый чих.
5. Я уже говорил, что почти все мои программы работают на Linux, Windows и OS X, а некоторые из них также на Android и iOS.
polifill
Доказательством может быть только опыт.
Лично вы работали в окружении опытных спецов — вам очень повезло.
Нет, не так. ВАМ ОЧЕНЬ СИЛЬНО ПОВЕЗЛО, ЕСЛИ ВОКРУГ ВАС ОДНИ ПРОФИ.
В реальной жизни это большая и большая редкость.
В среднем человек любой квалификации довольно посредственнен как профессионал.
То, что вы еще не сталкивались с огромным числом не профи — говорит о вашем малом жизненном опыте.
И только.
VioletGiraffe
Если бы вы внимательно прочитали мой комментарий на один уровень выше, вы бы увидели, что я работал в окружении обычных хороших програмистов, которые тоже понаписывали непортируемого кода, но жизнь, не без моей помощи, наставила их на путь истинный.
polifill
> что я работал в окружении обычных хороших програмистов, которые тоже понаписывали непортируемого кода
Тем самым вы и подтвердили мое утверждение.
> но жизнь, не без моей помощи, наставила их на путь истинный.
Разумеется, кто же будет делать лишнюю работу в здравом уме и твердой памяти?
С C++ этой работы слишком много, вот в чем дело.
VioletGiraffe
>Тем самым вы и подтвердили мое утверждение.
Какое? Что можно написать непортируемый код? Так я ещё два комментария назад сказал, что это так, но это не аргумент.
>C C++ этой работы слишком много, вот в чем дело.
Я так не считаю. Единственная серьёзная сложность была — избавиться от анонимных union.
polifill
> Я так не считаю. Единственная серьёзная сложность была — избавиться от анонимных union.
В вашем собственном коде.
А как же про сторонние библиотеки? Повезло?
Во многих других языках данная проблема просто и не может возникнуть. В других языках ты как правило борешься всего то с особенностями программной платформы (например Windows vs Unix).
В случае C++ — плюс еще и с самим языком.
Это не дело.
Не должно от «анонимных union» ничего зависеть. Это бага дизайна языка
VioletGiraffe
Нет. Это бага компилятора, что он компилит нестандартный код.
Библиотеки изначально брались только кросс-платформенные. Никакого везения, холодный расчёт.
polifill
В мире С++ компиляторы, частично поддерживающих тот или иной стандарт или поддерживающих нестандарт — никого не удивляют.
С другими языками таких разночтений — огромная редкость.
Как можно на столь зыбком фундаменте жить.
Это не совершенно непроизводственные затраты.
Вместо того, чтобы сосредоточиться на задаче — ты тратить часть своего времени на борьбу со своим инструментом.
0xd34df00d
Это не алогичность. Человек доказывает возможность применения языка в этой области, и это вполне можно делать своим примером.
А что до качества среднего программиста — что мне до этого, если я про свой уровень всё знаю и могу позволить себе выбирать место работы так, чтобы коллеги тоже были выше среднего?
polifill
Никто не спорит что С++ можно применять.
Мы обсуждаем статью — у С++ проблем не меньше, чем у С. Он не самый предсказуемый язык, не самый лучший для переносимости.
Требующей дополнительной квалификации и внимания для написания действительно переносимых программ.
По сравнению с Go, Java, Python…
Antervis
там, где надо писать на с/с++, уже не подойдут питон, го и джава
DeadKnight
На C++?
Правда там ответ на эти вопросы будет таким же.
Dark_Purple
За такие комментарии надо с HABRы выпиливать.
Akon32
Почему нет? Законы эволюции вполне действуют и на языки программирования. Паскаль почти вымер. Кобол почти вымер. А Си — слишком хорош, чтобы его выкинуть.
polifill
Си — слишком укоренился, чтобы его выкинуть.
А не слишком хорош.
Как минимум без Си исчезают все распространенные операционные системы, приличная доля компиляторов/интерпретаторов, туева хуча утилит и базовых библиотек без которых не сможет жить или создаваться софт более высокого уровня и пр. и пр.
Labunsky
Чем си не хорош-то? Хоть сам на него редко сажаюсь, но его лаконичность и простота каждый раз действуют на мозг очень приятно после какой-нибудь джавки. Наверное, лучше него с KISS ничто не сочетается, но это сугубое ИМХО
quwy
А огромного количества UB вам мало?
Labunsky
UB легко избегать. Особенно, если знаешь, что пишешь код под определенную архитектуру с определенной ОС, тогда его будет не больше, чем в любом другом языке
polifill
Си — это такой над-ассемблер. Таким его задумали, так надо было. И до тех пор пока на Си писали ядра операционных систем да драйвера — эти самые UB можно пережить.
Но когда речь идет о прикладном ПО, то планка совсем иная.
UB в Си побольше, чем в «любом языке», используемом для написания прикладного ПО, если в качестве любого рассматривать Python, Java, Go.
Если под «любым языком» вы понимаете C++ — тогда конечно, да, я с вами соглашусь.
Labunsky
Как я уже писал, это исключительно мое мнение, но большая часть UB, которое встречал — это работа с многопоточностью и асинхронностью. И в этом Си не сильно хуже других языков, иногда даже лучше
Плюсы для меня темный перегруженный лес, так сложилось исторически, поэтому сравнивать с ними не хочу и не берусь)
akzhan
Ну я бы не сказал, что в Python, Ruby, Perl намного меньше undefined behaviors. Все они основаны на Си-рантайме и также в основном исповедуют типа без привязки к разрядности архитектуры. Хотя есть и типы более жестко определенные, но тем не менее. И это только один из аспектов.
Например, большая часть языков основана сейчас на LLVM, GCC или JVM, и вынуждены следовать их UB.
Antervis
есть же uint16_t и прочие
akzhan
В языке их нет. Это просто определения в библиотеке, описываемые под известный компилятор и известные платформы.
Если компилятор, например, не определит макросы, по которым stdlib догадается о размерности базовых типов, то "всё пропало". А он, по стандарту, и не обязан это делать.
lieff
Вроде как stdint.h входит в C99 https://ru.wikipedia.org/wiki/Stdint.h. Соответственно можно проверить, есть ли uint16_t на данной платформе, если нет, то uint_least16_t обязан быть по стандарту всегда.
akzhan
uint_least16_t имеет совсем иное (не столь строго определённое) поведение. в частности, при битовых сдвигах.
lieff
Ну выше было что в стандарте нет, тем не менее в стандарте он все же есть, его может не быть на платформе и стандарт это предусматривает. Можно написать корректный код без UB, который работает и в случае наличия точного типа и когда его нет. Можно или 2 варианта кода или универсальный, который закладывается только на least. Так же в случае использования точного типа и его отсутствия — код просто не соберется. Так что все же имхо Antervis прав.
deviant_9
Добавлю ещё, что для стандартных типов тоже есть определённые гарантии (если уж мы вынуждены писать на с89), следующие из описания хедера <limits.h>:
0xd34df00d
Лаконичность и простота — это вам в хаскель, а не си.
VioletGiraffe
Главная проблема Си для меня — отсутствие классов = нет простого и понятного механизма управления сложностью проекта через абстракции.
Labunsky
Я привык заменять классы отдельными для структур .h файлами, состоящими из, собственно, структуры и функций. Расширять их можно уже в .c файлах, импортирующих соответсвующие заголовочные, получая таким образом что-то вроде наследования и контрактов. По удобству это совсем не то, конечно, но уже приятнее
VioletGiraffe
Логично. Лучше, чем ничего.
NeoCode
Зря вас минусуют. Я разрабатываю на С и С++ уже около 15 лет, начиная от микроконтроллеров и заканчивая сложными распределенными системами — и могу сказать — да, жаль.
И нет, я не предлагаю перейти всем на javascript:) Но необходимость создания универсального языка программирования для замены С и С++ назрела уже давно. А не происходит это из-за инертности мышления и так называемого груза унаследованного кода, который якобы никто не хочет переписывать. Господа, переписать его не такая уж и проблема, было бы на что. Программисты так или иначе постоянно переписывают код, адаптируют его, переписали бы и на единую стандартизированную модификацию Си, заодно кучу древних багов выловили бы. Ах да, бизнес же не хочет тратить лишнее время и платить лишних денег за непонятную работу… им же надо чтобы тяп ляп и в продакшен. Ничего, не обеднели бы.
Если не углубляться в тонкости, сам язык Си вполне хорош за исключением некоторых мелочей. В нем есть некоторые странные соглашения, которые не мешало бы отменить ради универсальности (имя массива это указатель на первый элемент массива — в результате массивы это не first class objects) и некоторые древние и опасные наросты (препроцессор, #define true false, инклуды и т.п.). В нем нет многих современных фич — что плохо. И в нем есть неопределенное поведение (примеры которого показаны в статье) — что очень плохо. Не так уж и сложно жестко прописать правила выравнивания структур или размеры базовых типов. Но нет — с каких-то там древних времен, когда о стандартизации никто не задумывался и писали компиляторы на коленке для себя, получилось что под какую-то архитектуру размеры базовых типов оказались нестандартные. И может сейчас и архитектуры-то этой нет, но тягомотина с undefined behaviour продолжает тянуться ради дурацкой совместимости.
iCpu
Позвольте мне только один нескромный вопрос: а возможно ли в принципе свести воедино все зоопарки устройств, под которые писали и пишут на си, в один единый строгий стандарт?
Все дырки из поста, они же являются не прихотью, а компромиссом. Эти дырки — не слабоумие, а чёткое понимание, что в одних устройствах есть возможность контролировать условия работы и порядок выполнения, а в других — нет. Что в одних устройствах дорого выделить маленький кусок памяти, а в других вообще все типы одного 8-битного размера, а процессор не может в умножение. Даже, казалось бы, явные прорехи, вроде неопределённости порядка выполнения операндов — компромисс по отношению к параллельным системам, в которых вычисление левой и правой части происходило бы одновременно в автономных контекстах с результатом «1». Действительно, переменная же не помечена разделяемой, почему нет, openMP бы так и сделал (если бы мы умудрились распараллелить эту строку).
Единственный вариант отказаться от рыхлого стандарта — заставить каждого производителя железа выпускать свою надстройку над шаблонным языком. А это приведёт к той самой проблеме сужения числа специалистов, их удорожания и падения качества производимого кода.
Единственное бесспорное место — макросы, заменить бы их более удобоваримым метаинструментом.
NeoCode
Возможно.
Если все типы одного 8-битного размера, то это не значит что нельзя сделать 16-битные типы из них. Просто программист будет знать, что 16-битные типы не нативные. Подобная ситуация есть с FPU: на некоторых микроконтроллерах FPU отсутствует, но никто не мешает объявить тип float или double. Другое дело что работать это будет медленно и нередко криво.
Более того, можно ввести понятие модульных возможностей языка. И подключать или отключать различные языковые модули в свойствах проекта (а к таковым я бы отнес floating point, длинную арифметику (GMP), исключения (кстати различных видов — SEH, DWARF, SJLJ и возможно какие-то еще), RTTI, различную рефлексию, динамическое выделение памяти, сборку мусора, многопоточность и т.д.). Если модуль используется в коде, но отключен в проекте — ошибка компиляции; или подключайте модуль, или переписывайте код так чтобы данные языковые возможности не использовались, либо пишите свой модуль (что тоже потенциально возможно).
iCpu
Вы же понимаете, что вы не избавляетесь от проблемы, а перекладываете её в другой карман. Я не спорю, в стандарте явно не хватает встроенных инструментов получения некоторых гарантий, хоть он и сделал не так давно приятный шаг вперёд, как с теми же типами.
Но, позвольте, разве модульность не устанавливается на уровне компилятора? Тот же AVR GCC проглатывает далеко не весь код, хотя бы по причинам организационным, 32 килобайта, все дела. Все ваши модули уже спрятаны в нём. в компиляторе, и иначе быть не может, а доступ к нестандартным методам всё равно получается после подключения платформозависимых заголовочников, к которым есть ассемблерный код. То есть вы ни капли не добавите ничего нового.
То, что вы предлагаете, оно должно продумываться на этапе разработки библиотеки. Конечно, есть проблема сужающихся возможностей, когда наши амбиции придавливает sizeof(int), но ведь есть ровно обратные ситуации. Это вопрос говнокода, не языка. Но если мы строим библиотеку, способную работать на разных архитектурах, то нам над ними придётся воспарить в некоторой, возможно. параметризуемой, абстракции. А иначе как?
NeoCode
Все верно, параметризируемая абстракция должна быть.
В простейшем случае если нам нужен просто int и все равно сколько там байт, так и пишем int. А если нужно что-то конкретное (для сетевого пакета или двоичного файла) то пишем int8 или int16 или int32.
Но параметризация должна быть явная, как и модульность. Вообще все должно быть явное, стандартизированное и общее для всех компиляторов.
iCpu
Мне очень интересно посмотреть на возможный синтаксис и, пожалуй, способ получения\расширения модулей. Собственно, механизм работы, как бы это могло быть. Вам не составит труда?
polifill
За 40 лет проблему не решили. Хотя ей занимались люди поумнее и вас и меня.
Уже не решится проблема. Очевидно.
polifill
Легко.
Компилятор, зависимый от платформы, который не должен пропускать подозрительные вещи.
В Си же можно накосячить «втихую».
iCpu
Во-первых, модифицированные компиляторы и так не пропускают подозрительные вещи.
Во-вторых, а что считать подозрительным? Подозрительно что-то может быть с точки зрения платформы, конкретного компилятора, но мы же определяем независимый от предрассудков язык…
Да даже порядок вычисления параметров метода, его неопределённость подозрительна, только если мы вычисляем их один за другим. А если все сразу параллельно, считая глобальные переменные разделяемыми между контекстами, а локальные — в зависимости от погоды на Марсе?
Конечно, случайно взятая библиотека в таких условиях не соберётся сразу без проблем. Но модификация библиотеки по отметкам компилятора/анализатора/отладчика «здесь может быть гонка, здесь буду синхронизировать» — куда проще, чем писать тот же SQLite с нуля только потому, что наше подмножество языка ну очень уж далеко уехало от их подмножества.
polifill
Си был создан для замены ассемблера при создании операционной системы.
Сейчас же его применяют и для прикладного ПО, где спускаться на столь низкий уровень нет необходимости (тем более сегодняшние компиляторы очень хорошо оптимизируют).
Но Си все так же остается всё таким же «ассемблерно-близким», хотя для большей части продуктов, создаваемых с его помощью — таких вольностей не требуется, они только увеличивают риски косяков…
Зачем мы пишем прикладное ПО на «почти ассемблере» до сих пор?
iCpu
Вы пишите? Если да, то вопрос риторический. Если нет, то, позвольте, в чей огород камень? В огород библиотечников? Но те же libusb и прочие — системное ПО, да и SQLite не особо выше уровнем, 90% сорцов — попытка натянуть сову производительности на глобус универсальности. И, посмотрите, очень даже неплохо получается. А статья — она именно о железячнике, который писал то самое системное ПО с его системными заморочками реального времени.
Или вы из «новой волны», которая пишет под Arduino на JavaScript?
Karpion
Если Вам не нравятся существующие языки — пожалуйста, создавайте свой язык. Затем создайте компиляторы языка для всех более-менее распространённых платформ; ну или сделайте язык настолько хорошим, что другие программисты заходят создать компиляторы. Не забудьте добиться высокой эффективности программ на этом языке на любой платформе. И затем убедите программистов использовать Ваш язык.
Как видите — совершенно ничего сложного; раз-два, и готово.
Создатели и последующие развиватели C пошли немного совсем другим путём: они знали, что есть процессоры разной архитектуры, иногда — совершенно дикой с т.з. людей, привыкших к i*86 и ARM. Поэтому в C было определено поведение программ в определённых случаях. И было чётко сказано, что за пределами этой области поведение программы м.б. всяким-разным.
Например, случай:
x = 1 << y;
правильно работает при y>=0. А при отрицательных y поведение не определено. Если Вы хотите, чтобы для отрицательных y выполнялось
x = 1 >> (-y);
то никто не мешает Вам написать проверку y на знак и выбрать соответствующее действие.
Более того: прямо на этапе компиляции можно проверить, как данная архитектура (процессор и компилятор) ведёт себя для данного случая. И можно отказаться компилироваться на «плохой» архитектуре. Или для «плохой» архитектуры можно сделать менее эффективный, зато точно работающий код.