В статье я выражаю свою точку зрения, если несогласны — обоснуйте в комментариях.
Цель данной статьи: указать на недостатки С и С++, которые мне очень не нравятся и побудить Вас использовать новую версию языка или возможно даже предложить какие-то идеи по улучшению стандарта.
Что ж, самое время разжечь холивар.
Я думаю все в курсе, что в курсе что в C++ ужасные строки. Особенно, если мы говорим о старом типе, в новом string многое было исправлено и улучшено, но до сих пор нет поддержки юникода(!).
В стандарте C++ 20 вроде как собираются вводить unicode строки.
С++ 20! И это несмотря на то, что С++ существует с 1983 года.
Откроем вашу любимую IDE и попробуем скомпилировать следующий код:
#include <iostream>
#include <cstdio>
int main()
{
char string [256];
std::cout << "Привет: ";
gets(string);
std::cout << "Вывод: " << string;
return 0;
}
UPD1: комментатор говорит, что кракозябры только на винде. Это точно, забыл об этом написать.
Но все равно неприятно.
Я компилировал в Dev Cpp, компилятор GCC.
Скомпилируем и видим:
Хороший вывод на экран, да?
А теперь давайте заменим char string[256] на char* string.
Я не говорю, что это должно работать, но компилятор должен был выкинуть ошибку как максимум.
Мы получили рабочую программу, которая зависла.
Лучше бы компилятор выкинул ошибку.
Причем вся фигня в том, что компилятор мало того, что скомпилировал ее, он еще не
вывел предупреждения.
Вот еще прикол:
#include <iostream>
using namespace std;
int main(){
int arr[100]={};
cout<<arr[101]<<endl;
return 0;
}
Что мы ожидаем? Компилятор скажет нам, что нельзя обратиться к 101 элементу массива, раз элементов всего 100. Но компилируем, запускаем и видим… 32765( по крайней мере на моем железе).
Мда.
А теперь давайте протестируем вот этот код:
int i = 5;
i = ++i + ++i;
std::cout<<i;
Как вы думаете, что он выведет?
Правильный ответ — зависит от компилятора.
В GCC это будет 14, но в зависимости от флагов оптимизации.
А в другом компиляторе это легко может быть 12…
Думаю, все знают что в С и в плюсиках куча синтаксического сахара, который далеко не всегда нужен.
Например
std::cout<<4["string"];
это валидный кодОн выводит n, так же как и
std::cout<<"string"[4];
Здорово, да?
А теперь о больном.
С++ и сеть.
Это 2 ооочень плохо состыкующихся понятия.
Попробуйте просто скачать изображение котика с Вашего любимого сайта с помощью стандартной библиотеки C++.
Это было невозможно до принятия стандарта С++ 17.
В той же стандартной библиотеки нельзя работать с JSON.
Отличный комментарий по этому поводу.
В целом работа с JSON в C++ сродни кошмару.Источник.
Думаете, условие всегда будет ложно?
if(sizeof ('a') != sizeof (char)){
//do something
}
Нет, Вы ошибаетесь.
Если скомпилировать это как с++ проект то условие скорее всего не выполнится.
Не должно.[1]
А если как Си проект, то в таком случае sizeof('a')==sizeof(int).
Вот такие дела.
[1] Вообще множество разных компиляторов C и C++ это тоже проблема.
Потому что множество решений нестандартизовано и они будут работать только в определенных компиляторах.
Например, 128 битные числа в C++. В gcc и clang есть тип __int128, в то время как в Visual Studio его нет, потому что он не является стандартом. Или, например, строки в Visual Studio.
String^ MyString3 = "Hello, world!"; //попробуйте скомпилировать в GCC
Или, например, в старичке Borland C++ Builder можно код, написанный на Object Pascal.
И таких моментов много.
Особую боль вызывает отсутствие списка пакетов Си и С++.
Что из этого следует? Используйте новую версию C++, например С++ 17 и некоторые проблемы будут решены.
Надо сказать, что в ближайшем конкуренте C++ — Rust нет большинства проблем из этого списка, например есть замечательный cargo, но разумеется он тоже неидеален.
А какие проблемы С и С++ знаете Вы?
Пишите в комментариях.
UPD: похоже многие люди неправильно поняли мою статью:
Я ни в коем случаи не хочу раскритиковать си/с++ и сказать пишите на расте.
Просто указываю на недостатки с/с++, т.к они немного достали самого.
У всего есть свои минусы, я просто поделился мыслями.
UPD2:
В комментариях многие пишут, что так работает ос и вообще это фичи. Ребята, вы неправы.
Тот же раст доказательство, что можно сделать язык системного программирования в котором не так просто выстрелить себе в ногу.
Просто в С/С++ полно легаси решений, которые никто не исправит, потому что это может сломать обратную совместимость.
И да, это мое мнение, оно субъективно.
Если Вы не согласны — лучше прокомментируйте, а не тупо минусуйте, потому что мнение всех нас
субъективно.
Комментарии (114)
Jouretz
10.12.2018 20:04int i = 5;
i = ++i + ++i;
std::cout<<i;
И что конкретно тут удивительного?
Преинкремент имеет более высокий приоритет чем сложение, и складывается результат.
++i в ячейке памяти — 6
++i в ячейке памяти — 7
7+7 = 14.
В ассемблер оно переводится как
movl $5, -4(%rbp)
addl $1, -4(%rbp)
addl $1, -4(%rbp)
sall -4(%rbp)
Компилятор провёл лёгкую оптимизацию за нерадивым программистом и вместо сложения числа с самим собой просто умножил его на два.Enmar Автор
10.12.2018 20:19-4Я это почерпнул из какой-то статьи на хабре.
Вот из этой кажется:
habr.com/post/88185
Tyiler
10.12.2018 20:11+9смысл статьи в чем?
кто вы такой, сколько у вас лет опыта C/C++? — ваше мнение релевантно?
не первая уже статья такого толка, вы просто за компанию решили бросить на вентилятор чтоли?
так понимаю очередное проталкивание Rust.
я вот, например, после таких статей начинаю от него воротить нос, и желание пропадает даже смотреть что там у вас творится в Rust.Enmar Автор
10.12.2018 20:13-5Я ни в коем случаи не хочу раскритиковать си/с++ и сказать пишите на расте.
Просто указываю на недостатки с/с++, т.к они немного достали самого)picul
10.12.2018 20:41Вы указываете на особенности языка, не считая нескольких откровенно бредовых выражений (типа «строки в Visual Studio»). А то что это недостатки — это Ваше личное мнение.
RussDragon
10.12.2018 23:39Вообще, о Расте в последнее время и правда много хорошего слышу. Но никак не дойдут руки его посмотреть :(
BkmzSpb
10.12.2018 20:15+2String^ MyString3 = «Hello, world!»; //попробуйте скомпилировать в GCC
А вы в курсе, что это C++/CLI? Ну т.е. это полу-управляемый C++, живущий с .NET? И что вашString^
это скорее всего дотнетовскийSystem.String
(когда-то давно я на этом даже что-то писал, но могу ошибаться)? Более того, уVC++
есть свои инструменты для работы со строками, как часть winapi,wchar
иtchar
, первый из которых 16-битовый тип, а второй определяется в зависимости от определенного символа#ifdef UNICODE
(отсюда).Enmar Автор
10.12.2018 20:16-1Вот и я про тоже.
Нельзя просто взять любой c++ код и быть уверенным, что он во всех компиляторах будет работать и при этом одинаковоBkmzSpb
10.12.2018 20:19+3Да как бы это сказать… На мой взгляд
C++/CLI
даже плюсами назвать нельзя. Это как… компилироватьCUDA
безnvcc
и жаловаться, что компилятор не понимаетfunction_name<<<n, m>>>(args)
. Это фактиечски диалекты.
valexey
10.12.2018 20:18+4Ну, блин. Ну нельзя же так:
Откроем вашу любимую IDE и попробуем скомпилировать следующий код:
#include <iostream> #include <cstdio> int main() { char string [256]; std::cout << "Привет: "; gets(string); std::cout << "Вывод: " << string; return 0; }
ok. Откроем:
$ cat main.cc #include <iostream> #include <cstdio> int main() { char string [256]; std::cout << "Привет: "; gets(string); std::cout << "Вывод: " << string; return 0; }
Теперь попробуем собрать и запустить.
Итак, во-первых компилятор жутко ругается на этот код, намекая, что он, мягко говоря, плох:
ругань компилятора$ g++ main.cc main.cc: In function ‘int main()’: main.cc:8:5: warning: ‘char* gets(char*)’ is deprecated [-Wdeprecated-declarations] gets(string); ^ In file included from /usr/include/c++/5/cstdio:42:0, from main.cc:2: /usr/include/stdio.h:638:14: note: declared here extern char *gets (char *__s) __wur __attribute_deprecated__; ^ main.cc:8:5: warning: ‘char* gets(char*)’ is deprecated [-Wdeprecated-declarations] gets(string); ^ In file included from /usr/include/c++/5/cstdio:42:0, from main.cc:2: /usr/include/stdio.h:638:14: note: declared here extern char *gets (char *__s) __wur __attribute_deprecated__; ^ main.cc:8:16: warning: ‘char* gets(char*)’ is deprecated [-Wdeprecated-declarations] gets(string); ^ In file included from /usr/include/c++/5/cstdio:42:0, from main.cc:2: /usr/include/stdio.h:638:14: note: declared here extern char *gets (char *__s) __wur __attribute_deprecated__; ^ /tmp/ccmI9pjb.o: In function `main': main.cc:(.text+0x34): warning: the `gets' function is dangerous and should not be used.
Enmar Автор
10.12.2018 20:22-5Окей, согласен, не кроссплатформено.
Но все равно неприятно, что такое происходит, пусть и только в винде.valexey
10.12.2018 20:33+5Статья выглядит очень и очень сырой. И общее ощущение, что автор не разобрался что к чему и что языка, который он критикует, он не знает. Как работает операционная система, которой он пользуется, тоже не знает.
Поэтому критика получается слабой и не убедительной.Enmar Автор
10.12.2018 21:01-2В чем не разобрался?
Конкретнее пожалуйста.
Вы говорите так работает ос.
А я вам, что компилятор си/с++ мог бы не позволять так просто выстрелить в ногу.geher
10.12.2018 22:30+1Проблема в данном конкретном случае не в компиляторе, а в программисте, при незначительном вкладе ОС, использующей одновременно три кодировки (а иногда и больше) в разных местах.
Если вы выводите строку в одной кодировке в системе, которая работает с другой кодировкой, то получите ровно ту же проблему абсолютно на любом языке программирования.
Причем надеяться на автоматическое перекодирование (типа у нас язык умный, он может) не приходится, поскольку далеко не всегда возможно корректное преобразование между кодировками (например, попробуйте перекодировать кириллицу в кодировке 1251 или даже UTF-16 в кодировку 1252).
dlinyj
11.12.2018 09:34Сложно человеку разъяснить уровень его некомпетентности, если он не готов даже признать то что он не компетентен.
Как минимум прочитайте про кодировки в различных ОС. Прочитайте про разницу с и с++.
Почему компилятор «стреляет» в ногу, так это потому что вы не умеете писать. Используйте другие языки. Кстати, бывают компиляторы с защитой. dihalt у себя на сайте давал пример.
berez
10.12.2018 20:41+1Преимущество языка С++ в том, что вас никто не заставляет использовать стандартную библиотеку. Хотите истинной кроссплатформенности — используйте кроссплатформенные библиотеки и фреймворки а-ля Qt. Там и юникод, и окошечки, и шашечки, и рюшечки, и сигналы-слоты — все есть.
А стандартная библиотека — да, местами кривовата и недостаточно полна. Но это не потому, что язык С++ чем-то плох, а потому, что язык не навязывает какую-то одну реализацию.
И да, язык С — это уже давным-давно совсем другой язык. То, что он немножечко совместим с С++ на уровне вызовов и имеет похожий синтаксис, еще не делает его подмножеством С++.
myxo
10.12.2018 21:36+2И тут вы ошиблись. Это не «только в винде», а в вашем ненастроенной консоли. Если вы на той же ubuntu, сохраните cpp файл (а значит и вашу строку) в кодировке cp1251, и выведете в консоль с настройкой utf-8, то получите такую же ерунду (точнее не такую же, но ерунду).
Именно об этом и писал valexey.
vilgeforce
10.12.2018 20:20Если автору не нравится возможность читать/писать произвольную память — это автор до такого недозрел, а не языки плохие. Возможность работать с байтами в памяти «как есть» — огромный плюс.
Enmar Автор
10.12.2018 20:24Это вы про arr[101]?
Только не говорите, что это фича, я Вас прошу.
В том же расте или в любом другом более высокоуровневым языке такого не будет.vilgeforce
10.12.2018 20:30-3Конечно фича!
struct foo{
unsigned char low[128];
unsigned char high[128];
};
И запись в foo.low[] с явным выходом за его границы — фича. Я в зависимости от старшего бита индекса пишу в один или в другой массивы. БЕЗ ветвлений, сравнения битов и тому подобного.geher
10.12.2018 22:21+1Встречал еще веселее в продуктах IBM (в заголовочных файлах библиотек):
что-то похожее на такое:
struct foo{ int headerField1; int headerField2; unsigned char data[0]; // тут память подводит, может быть и [1], // но память все равно рисует [0] };
0xd34df00d
10.12.2018 23:18+1На самом деле это не фича. Это UB даже в этом контексте, и это может очень больно выстрелить.
Успехов с апгрейдами компилятора, если вы считаете такой стиль допустимым.vilgeforce
10.12.2018 23:56-2Да и ладно :-) Код будет решать задачу на конкретной (программно/)аппаратной платформе корректно и быстро, что и требовалось.
0xd34df00d
11.12.2018 00:09+1Я бы застремался пользоваться такой аппаратной платформой. Да и компилятор, видимо, вы действительно не будете обновлять больше никогда.
А вообще, конечно, грустно это. Потом у нас Therac-25 получается, тойоты ускоряются, ещё всякая ерунда происходит. Зато на С и быстро.
0xd34df00d
11.12.2018 00:08+2Несогласие вижу я. Ну хорошо, будем разбираться, благо чем С хорош — всегда можно открыть стандарт.
6.7.2.1/14: «There may be unnamed padding within a structure object, but not at its beginning.»
То есть, как минимум, для начала непонятно, куда именно вы там пишете: никто не мешает реализации вставить произвольный padding между
low
иhigh
.
6.5.2.1/2: «The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))).»
6.5.6/8: «When an expression that has integer type is added to or subtracted from a pointer, the
result has the type of the pointer operand. If the pointer operand points to an element of
an array object, and the array is large enough, the result points to an element offset from
the original element such that the difference of the subscripts of the resulting and original
array elements equals the integer expression.»
«If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.»
То есть, когда вы просто даже создаёте указатель внутрь
high
черезlow
, то у вас уже UB, даже если вы ещё ничего никуда не успели записать или прочитать.
Писать вне array object'а тоже UB, но это уже чуть больше цитировать надо.
Так что поздравляю, у вас в таком простом коде три UB.
myxo
10.12.2018 21:01-1Это не баг и не фича. Просто так было раньше, никто переделывать не будет (и слава богу).
Проблема скорее в том, что существует масса старых учебных материалов, в которых новичкам пишут использовать обычные массивы вместо std::array (ex boost::array).Enmar Автор
10.12.2018 21:07-3Просто так было раньше и переделывать не будут.
Это называется legacy)
И это багmyxo
10.12.2018 21:20legacy — это не баг, это объективная реальность мира. Если у вас завалялось несколько лишних десятков миллиардов $, то вы можете их потратить на переписку и тестирование всего существующего софта на с/с++. Тогда можно будет со спокойной душой убирать возможность написать std::cout<<«string»[4]; из стандарта. Оно того стоит?
Alex_ME
10.12.2018 22:35Вы предлагаете добавить к каждому обращению к массиву проверку границ? Знаете, как это отразится на производительности. Не говоря уже о том, что это, фактически, обращение по указателю со смещением. Можете использовать высокоуровневые классы, чтобы избежать такого.
Точно так же с неопределенным поведением. Это цена производительности во многом.dev96
10.12.2018 22:41Автор говорит о том, что в таком compile-time случае компилятор (а не стат. анализатор) по рукам не даст.
Проверка в рантайме в плюсах итак включается в дебаг сборке или принудительно в релизе.
Либо, опять-таки статический анализ.
Хотя, теоретически, std::array во время компиляции может ругаться.darkxanter
10.12.2018 23:11Статическим анализом в любом случае хорошо пользоваться.
clang выдает предупреждения на выход за границы массива в приведенном примере в статье.dev96
10.12.2018 23:19Абсолютно с вами согласен.
MSVC тоже предупредит, если настройки подкрутить.
Причем сейчас активно начинают везде внедряться проверки «C++ Core Guidelines».
Pancir
11.12.2018 09:22Возможность указать индекс который лежит за пределами блока памяти разрешает вам использовать этот блок в стандартных алгоритмах.
// Я бы так писать не стал // но возможность есть, на случай например // использования сторонней библиотеки. const std::size_t arr_len = 100; int arr[arr_len]; std::fore_each(arr, arr[arr_len], Sum()); // вот так уже сильно лучше std::array<int, 100> arr; std::fore_each(arr.begin(), arr.end(), Sum()); // ошибка исключена // или for(const auto & val : arr){ ... // ошибка исключена } // или ... = arr.at(200) // exception
Если не уверены используйте метод at для доступа к элементу массива, он выкинет исключение при out of range.
Я так понимаю с приходом constexpr вы можете сделать свою реализацию статического массива где в операторе [] можете проверить out of range на этапе компиляции и выкинуть error, не могу уверенно сказать т.к. constexpr еще не изучал подробно.
valexey
10.12.2018 20:22+2В gcc и clang есть тип __int128, в то время как в Visual Studio его нет, потому что он не является стандартом.
А у раста вообще НИЧЕГО стандартом не является, так как стандарта на Rust НЕТ. Хотя, быть может я ошибаюсь, и у раста есть ISO стандарт? Можно узнать его номер?
И еще — у раста ровно один компилятор. Понятно что сам с собой он, в основном, совметим. Хотя, насколько я помню, если использовать только стабильные фичи раста, то половина библиотек просто не соберется, так как они юзают «нестандартные», то есть, не стабильные расширизмы компилятора и языка. И это при одном единственном компиляторе раста!
Что же будет, когда у раста появится хотя бы три независимые реализации?Enmar Автор
10.12.2018 20:26+1Вроде у них нет ISO.
Но 128 битные числа будут гарантировано в кажой раст реализации)
Стандарт раста это доки на их сайте.
Все что там написано будет работать в любом нормальном компиляторе раста.valexey
10.12.2018 20:37А сколко их, нормальных компиляторов раста? Ну хотя бы сколько разных фронтендов у него?
Еще раз — стандарта на Rust нет. Вообще, в мире довольно мало языков на которые есть живой актуальный стандарт. Могу назвать всего 5 штук: Си, С++, Ада, Фортран, Кобол. Все эти языки максимально дотошно описаны и стандартизированы. Стандарты на все эти языки регулярно обновляются.
Может какие-нибудь языки забыл, поправьте если вдруг еще какой-то язык имеет актуальный ISO стандарт.argentumbolo
10.12.2018 21:04+1На самом деле их немало — https://en.wikipedia.org/wiki/Category:Programming_languages_with_an_ISO_standard
Даже на Ruby есть ISO/IEC 30170valexey
10.12.2018 21:14Там, к ISO стандартам, есть требование, что они должны обновляться каждые N лет (лет 8 вроде), иначе стандарт протухает. В принципе обновление стандарта на ЯП может быть чисто формальным, но оно должно быть. Поэтому например у паскаля сейчас нет ISO стандарта актуального.
У ECMAScript'а, я смотрю, есть, а вот у Модулы-2, нет. У пролога — тоже нет. Ну и далее, по списку.
А вот про руби не знал, спасибо. Если про ECMAScript я просто забыл, то про руби я вообще не был в курсе, что там стандарт ISO есть.
TargetSan
10.12.2018 23:07+2Вы знаете, я как постоянный крестоносец смотрю на этот стандарт каждые три года, и как-то грустно становится… Зато добавили эллиптические кривые и космолёт...
RussDragon
10.12.2018 23:57Честно говоря, как человек, который писал 3 года на ++, я с радостью с него убежал и в последнее время с ужасом наблюдаю за тем, что добавляется в стандарт. Нет, я не готов критиковать их аргументированно и с позиции «что было бы лучше сделать», но лично меня передёргивает, когда я вижу новые трехсимвольные операторы.
berez
11.12.2018 00:11+1лично меня передёргивает, когда я вижу новые трехсимвольные операторы.
Дайте угадаю: на перле вы никогда не писали? :)
0xd34df00d
11.12.2018 01:02Это всяко лучше, чем писать по 6 функций на каждую пару сравнимых типов.
Триграфы, кстати, из C++17 удалили.TargetSan
11.12.2018 01:32С одной стороны да. С другой стороны это решение одной частной проблемы внедрением нового синтаксиса.
0xd34df00d
11.12.2018 02:14Общую проблему решат метаклассами и компилтайм-рефлексией, но до них ещё далеко, да.
picul
10.12.2018 20:27+22А какие проблемы С и С++ знаете Вы?
Самая большая проблема C++ — его постоянно критикуют те, кто в нем не разбирается.Enmar Автор
10.12.2018 20:40-5Пожалуйста, скажите в чем именно я не прав?
Lofer
10.12.2018 21:04+1но до сих пор нет поддержки юникода(!).
Просто указываю на недостатки с/с++, т.к они немного достали самого)
Пожалуйста, скажите в чем именно я не прав?
Присоединюсь к предыдущим ораторам, в вопросах кроссплатформености, размеров типов, понимания ОС и т.д. и задам только один вопрос (из двух пунктов): Вы с каким юникодом собирались работать или C++ должен за Вас магическим образом догадаться и сделать всю сервисную работу, которые делает «обычный язык» программирования?
picul
10.12.2018 21:12Про перлы типа любимой IDE и строк в Visual Studio Вам уже сказали, в остальном — Вы не правы в том что это недостатки. Это особенности языка, которые введены не просто так, и большинство из которых до сих пор имеет смысл. То что от них отказывается Rust — это камень в огород Rust'а.
Enmar Автор
10.12.2018 21:57-1черт, скажите мне КАКИЕ ЭТО ОСОБЕННОСТИ?
То, что предпроцессор ужасен и компилятор компилирует программу которая ломается при
работе, проще говоря runtime error хуже compile error.picul
10.12.2018 22:08Run-time error — это всегда хуже, чем compile-time, то что Вы это не понимаете — ставит под сомнение Ваш опыт программирования вообще, а не только на C++. И чем ужасен препроцессор?
Lofer
10.12.2018 22:29черт, скажите мне КАКИЕ ЭТО ОСОБЕННОСТИ?
К примеру:
1. первый вопрос: размер типов. (тупо сколько занимает в памяти и почему именно так)?
2. второй вопрос: что такое String?
3. третий вопрос, вытекающий их первых двух: если юникодов как грязи UTF-8, UTF-16 BE, UTF-16 LE, UTF-32 и т.д. то как С++ должен решить за Вас задачку «Сколько памяти выделить и как кодировать в памяти?» если Вы сами не понимаете что творите?
MaxVetrov
10.12.2018 20:32Ну вообще, это все равно что писать «Что мне не нравится в ассемблере.»
Зачем смешивать ввод-вывод с и c++?
#include <iostream> #include <cstdio>
Используйте std::string и будет вам счастье.
Есть груз прошлого, от него нужно избавляться.
Нашел проблему — нет before_end(итератор на последний элемент) в forward_list, для быстрого добавления в конец односвязного списка элемента(ов). В принципе можно решать эту задачу на этапе заполнения, но корявое решение конечно же. Возможно это исправят в будущем.Enmar Автор
10.12.2018 20:47-1Смешивать не нужно было, но в принципе это не на что не влияет)
0xd34df00d
10.12.2018 23:23+1Влияет, потому что если вы читаете в
std::string
, то такой проблемы нет.
NeoCode
10.12.2018 20:38К сожалению, разработчики стандарта очень боятся нарушить обратную совместимость.
А в случае со строками — правильным решением было бы вообще отвязать строки от кодировки. То есть «Hello» это строка, а кодировка определяется из настроек проекта и компилятора. А вот если требуется явно указать кодировку — то используются префиксы. Причем для однобайтовых кодировок (которые, несмотря на Unicode, иногда все-же нужны) можно было бы указывать кодировку явно. А если она указана обобщенно (т.е. префикс, означающий «текущая однобайтовая кодировка») то брать указанную в настройках проекта или компилятора. Тогда знаменитый вопрос о крякозябрах при выводе русских строк из консольных программ в винде потерял бы актуальность:)
Примерно аналогично должно быть и с числами. Число 42 — это просто число, а его тип должен выводиться компилятором каждый раз в зависимости от контекста. Это может быть и byte, и int, и unsigned long, и double, и даже какой нибудь mpf_t из GMP.Enmar Автор
10.12.2018 20:43Да, именно из-а того, что
разработчики стандарта очень боятся нарушить обратную совместимость
в си и в С++ много легаси решений.
NeoCode
10.12.2018 21:32Это решается какой нибудь #pragma version 2.0 в начале каждого файла. Кому лень переписывать — пускай мучаются на старом, кому не лень — пользуются современным языком с исправленными ошибками дизайна.
Enmar Автор
10.12.2018 21:41Прагмы ни для всего придумали)
Как можно было выстрелить себе в ногу, так и сейчас можно.NeoCode
10.12.2018 23:13Так это на любом языке можно. Однако стремление к созданию «абсолютно безопасного языка» в ущерб всему остальному (а иначе не получится) я не считаю правильным.
myxo
10.12.2018 21:11Попробуйте просто скачать изображение котика с Вашего любимого сайта с помощью стандартной библиотеки C++.
Это было невозможно до принятия стандарта С++ 17.
Эм… я что-то пропустил? Networking TS же вообще на с++23 отодвинули.
С++ и сеть.
Это 2 ооочень плохо состыкующихся понятия.
#include <boost/asio.hpp> // можно заменить на вашу любимую библиотеку
Не мешайте в одну кучу проблемы с++ и проблемы стандартной библиотекиEnmar Автор
10.12.2018 21:16-2Вроде как приняли в 17.
Но я могу ошибаться, суть в том, что это появилось совсем недавно или не появилось еще даже.
А проблемы стандратной библиотеки языки разве не относятся к проблемам языка?
Если в языке плохая стд либа, то плохо язык.
Amomum
10.12.2018 21:12Большая часть вещей, на которые вы жалуетесь, исправляется библиотеками и опциями компилятора.
На многие проблемы можно получить предупреждение, если компилировать с -Wall -Wextra. Не забываем про статические анализаторы!
Для проверок в рантайме есть -fsanitize (который прекрасно отлавливает выходы за границы массивов) и valgrind.
Не поленитесь изучить опции компилятора, которым вы пользуетесь.
Под спойлером - мой набор ключей для gcc, добавьте -fsanitize по возможности:-Wall -Wpedantic -Wextra -Wcast-align -Wcast-qual -Wvla -Wshadow -Wsuggest-attribute=const -Wmissing-format-attribute -Wuninitialized -Winit-self -Wdouble-promotion -Wstrict-aliasing -Weffc++ -Wno-unused-local-typedefs
Enmar Автор
10.12.2018 21:19-4Я знаю про опции компилятора)
Но согласитесь, круто когда все работает без опций.
Это же основа, не что-то эдакое.
Когда я в IDE компилирую проект, то очень часто я не могу задать опции компилятора.Amomum
10.12.2018 21:21+2Но согласитесь, круто когда все работает без опций.
Вам шашечки или ехать? Конечно, было бы здорово, если бы часть из этого была включена по-умолчанию. С другой стороны, очень много проектов тогда начали бы страдать от ложных срабатываний.
Когда я в IDE компилирую проект, то очень часто я не могу задать опции компилятора.
Это что у вас за IDE такая? О_оEnmar Автор
10.12.2018 21:33+1Вам шашечки или ехать? Конечно, было бы здорово, если бы часть из этого была включена по-умолчанию. С другой стороны, очень много проектов тогда начали бы страдать от ложных срабатываний.
Ложные срабатывания = плохая реализация.
Еще раз говорю, посмотрите на раст.
Там все лучше с этим.
Там не так просто в ногу выстрелить, ложных срабатываний там нет.Amomum
10.12.2018 21:40Я смотрю на раст последние года 4, спасибо. Но в продакшене пока что его применять не имею возможности.
А в ногу выстрелить можно на любом языке, не обольщайтесь. Посмотрите хотя бы на это.
И все же, что у вас за IDE такая? IDE1886 что ли?
DollaR84
10.12.2018 21:31+1>>> А теперь давайте заменим char string[256] на char* string.
Ну знаете ли… Заменить массив символов на указатель на символ и ожидать от такой замены одинакового результата…
Я вообще думаю, что понимание указателей и работы с ними — это мощный инструмент. И люди, пришедшие в C/C++ из других языков пока не поймут указатели — вообще не знают C/C++.Enmar Автор
10.12.2018 21:34Я не говорю, что этот код должен работать!
Я говорю, что компилятор должен был сказать, что мол ты фигню мне подсунул.myxo
10.12.2018 21:45+2Так он и говорит. Разве что нужен современный компилятор, который сразу ругнется на gets и опции санитайзера, который ругнется на использование невыделенной памяти. Почему это не является настройкой по-умолчанию? Потому-что Си — язык для других задач, и тем кто пришел с, например, питона, это не понятно.
Вы вполне можете использовать указатель, не выделяя под него память и писать туда что-то. И это не будет ошибкой, если вы, например, направили указатель в буфер видеокарты. Компилятор не может (и не должен) знать о том, что определенная область в адресном пространстве — это мапа в видеопамять и писать туда можно. И Си нужен был именно для этого, это его дефолтовое поведение.
darkxanter
11.12.2018 09:49А теперь давайте заменим char string[256] на char* string.
Я говорю, что компилятор должен был сказать, что мол ты фигню мне подсунул.
clang и gcc выдает предупреждение о неинициализированной переменной.
Нужно просто добавить вывод всех предупреждений флагами компилятора.
dbagaev
10.12.2018 21:38Если скомпилировать это как с++ проект то условие скорее всего не выполнится.
А если как Си проект, то в таком случае sizeof('a')==sizeof(int).
А если использовать компилятор FORTRAN, то в таком случае этот код вообще не скомпилируется!
Выше уже много написали про конкретные пункты, я просто замечу, что критика любого языка в стиле «мне в языке A не нравится фича Х, но зато в языке B этой проблемы нет» обычно свидетельствует о том, что утверждающий знаком более или менее с языком B, а с языком A — ровно настолько, а чтобы не знать, как решаются в нем конкретные проблемы, а главное, почему они решаются именно так. Ну а это отличный повод начинать холивары.
Вместо холивара изучите лучше оба языка, Rust и С++, они оба вам пригодятся.Enmar Автор
10.12.2018 21:51+1Т.е Вы утверждаете, что это фича?
Скажите тогда пожалуйста, зачем было так делать?argentumbolo
10.12.2018 22:48Затем, что 16...32-х битный процессор не имеет специальных регистров для чаров.
Он хранит их в младшей части обычных регистров размером sizeof(int).
И когда вы загружаете туда чар, вам следует помнить, что при сравнении с другими типами — char(0x32) это абсолютно то же саме, что и int32(0x00000032).vilgeforce
11.12.2018 00:05Либо я вас не так понял, либо все же «char(0x32) это абсолютно то же саме, что и int32(0x00000032)» — неверно. Сплошь и рядом mov AL, byte ptr[ESI + ECX]; cmp AL,0x32 и подобное наблюдаю. Да, содержимое EAX при таком присвоении уничтожается, но сравнивается все же именно AL
rogoz
11.12.2018 03:16mov AL, byte ptr[ESI + ECX];
содержимое EAX при таком присвоении уничтожается
Нет.vilgeforce
11.12.2018 10:04Да натурально! Лежало в EAX 0x12345678, стало 0x123456MN — значение уничтожено и восстановлению не подлежит, часть регистра перезаписана
dbagaev
10.12.2018 22:55Я утверждаю, что вы не понимаете С++. В С++ согласно последнему стандарту размер char всегда равен 1, а 'a' — это char, поэтому условие всегда будет true. Возможно, это не было верно для каких-то платформ и компиляторов в прошлом, но сейчас все три доступные мне компилятора показывают 1 для обоих sizeof() для 32 и 64 бит.
В случае с С размер int платформенно-зависимый, и для каких-то платформ действительно может равнятся 1, а не привычному нам 4, или 2 некоторое время назад. И это действительно фича, так как int в С определяется физическим размером регистров процессора целевой платформы.
Я даже более вам скажу, не во всех архитектурах char может быть 8 бит. Кстати, тут мне стало дейтвительно интересно. Компиляторы С уществуют для огромного количества самых экзотических аппартных платформ, появлявшихся и исчезавших последние 40 лет. И именно поэтому язык допускает такие вольности, а не потому что код должен быть переносим. А Rust, для какого количества платформ существуют компиляторы и на чем они написаны?argentumbolo
10.12.2018 23:04Не, тут Enmar прав относительно Си.
В Си sizeof char литерала равен sizeof(int), хотя sizeof char переменной всё ещё равен единице.
Ну то есть:
char ch = 'Z'; assert(sizeof(ch) == 1); assert(sizeof('Z') == sizeof(int));
Usul
11.12.2018 07:52«Исторически так сложилось»
Why are C character literals ints instead of chars? (StackOverflow)
The reason is that the definition of a literal character has evolved and changed, while trying to remain backwards compatible with existing code.
In the dark days of early C there were no types at all. By the time I first learnt to program in C, types had been introduced, but functions didn't have prototypes to tell the caller what the argument types were. Instead it was standardised that everything passed as a parameter would either be the size of an int (this included all pointers) or it would be a double.
This meant that when you were writing the function, all the parameters that weren't double were stored on the stack as ints, no matter how you declared them, and the compiler put code in the function to handle this for you.
This made things somewhat inconsistent, so when K&R wrote their famous book, they put in the rule that a character literal would always be promoted to an int in any expression, not just a function parameter.
When the ANSI committee first standardised C, they changed this rule so that a character literal would simply be an int, since this seemed a simpler way of achieving the same thing.
When C++ was being designed, all functions were required to have full prototypes (this is still not required in C, although it is universally accepted as good practice). Because of this, it was decided that a character literal could be stored in a char. The advantage of this in C++ is that a function with a char parameter and a function with an int parameter have different signatures. This advantage is not the case in C.
This is why they are different. Evolution…
ZaMaZaN4iK
10.12.2018 21:42+4Хорошая попытка фарма минусов.
justhabrauser
11.12.2018 10:06Не попытка, а реальный, качественный фарм.
Накосил минусов — не унести.
dev96
10.12.2018 22:30+1Особенно, если мы говорим о старом типе, в новом string многое было исправлено
и улучшено, но до сих пор нет поддержки юникода(!).
Нет адекватной поддержки только UTF8.
wchar_t (платформенно-зависимый) был всегда.
В C++11 появились char16_t(UTF16) и char32_t(UTF32) и соответствующие им литералы, которые как-бы ЮНИКОД.
Причем кроссплатформенную и самую адекватную строку делают на UTF16.
Да, здесь нам дают выбор. И во многих случаях выбор падет на UTF16 или ASCII (сугубо английский), а не на UTF8 переменного размера.
Вы используете ASCII-строки (однобайтные), а конкретно CP1251 и возмущаетесь на кракозябры.
Это самая банальная вещь, на которой вы и посыпались.marsianin
10.12.2018 22:37+1Так-то не все unicode-символы влезают в 16 бит, так что utf16 тоже переменной длины.
acmnu
11.12.2018 09:08Я тут погуглил, и не могу не заметить, что все же char<x>_t являются не вполне переносимыми из-за LE/BE. https://stackoverflow.com/questions/31433324/char16-t-and-char32-t-endianness. Это, в принципе, описано в стандарте.
Например, с этим можно столкнутся при попытке организовать бинарный протокол между C++ и Java. У последней внутренее бинарное преставление всегда BE (наследие Sun, насколько я понимаю).
LexS007
11.12.2018 01:04Думаю, все знают что в С и в плюсиках куча синтаксического сахара
Что? Какой в Си сахар-то?artemisia_borealis
11.12.2018 01:41+1Цикл for, наверное, имеется ввиду. Иначе бы пришлось if'ом проверять условие и использовать goto. И среди while и do-while что-то одно (по меньшей мере) является сахаром.
Ну, и пустая директива, конечно. Хотя это уже не сахар, а скорее амфетамин…
mapron
11.12.2018 04:09+1Ну что накинулись-то на автора. Он же в начале предупредил — профессионалам не читать! Читать только тем, кто не разбирается!
Если серьезно, к автору много вопросов.
1. Если это просто желание поделиться радостной эйфорией от первых же граблей, на которые наступил в языке — то явно ресурс выбран неудачно. Лучше публиковать на говнокод ру, например. Там еще можно в пост добавить слово «крестобляди», там вообще такое очень любят.
2. Если это троллинг аудитории хабра, шоб пригорело — то крайне неумелый. Вы пишете такие вещи, от которых фейспалм будет скорее, чем пригорание.
3. Если это не троллинг а честная агитация за Rust — то правильной статьей было бы «вот смотрите какая тупая конструкция на С, а вот на расте пишешь то-то и граблей не собираешь». Статья вышла бы холиварная, но хоть какое-то признание вы бы получили.
И да, всевозможные «плюсики» обороты в тексте совсем не приемлемы для нейтрального принятого стиля на Хабре.
Всего наилучшего.
sami777
11.12.2018 10:18Помню, когда выбирал для себя ООП язык, то остановился на плюсах. Но, когда посмотрел сколько под них сред и когда оказалось, что интернет помогает всегда неправильно, то в итоге ушел на шарп, о чем абсолютно не сожалею.
Насчет Си все намного легче. Под встроенные системы альтернативы нет.
snuk182
Как-то очень толсто даже для нелюбителей си.
Enmar Автор
В смысле?
snuk182
В самом прямом.
Требование поддержки JSON в стандартной библиотеке — апофеоз глупости для низкоуровневого языка. Обращение к символам юникодной строки по индексу, равно как определение ее длины по размеру массива — это позор. Наезд на sizeof() — это вообще демонстрация полного непонимания работы платформозависимых типов. Пожалуйста, учите матчасть перед написанием поста.
По теме. Оба языка достаточно старые, с кучей легаси. Тот факт, что они живы и активны до сих пор, говорит о том, что для этого есть веские причины. Работа с юникодом из коробки — для этих языков далеко не самое важное. Если новый проект в 2019 году стартует на С/С++ — это как минимум понимание бизнес-целей, рисков и стоимости разработки. Я сам отношусь к людям, которые не против видеть Rust в списке технологий разработки вместо C, но могу сказать, что вышеописанным вы оказываете Rust медвежью услугу. Равно как и то, что эти два мамонта из своих ниш выбить крайне маловероятно, и в принципе не очень разумная затея.
Enmar Автор
Вот здесь с Вами не согласен.
Я понимаю, что размер int разный от платформы к платформе.
Но странно, что в си компиляторе это воспринимается так, а в ++ компиляторе по-другому.
Здесь я с Вами согласен.
Ядро юникса и винды например на сях.
snuk182
Потому что это два разных языка.
Enmar Автор
Я знаю, что С++ не является разновидностью Си.
Но несовместимости между 2 этими языками все равно зло.
RussDragon
> Я знаю, что С++ не является разновидностью Си
> несовместимости между 2 этими языками все равно зло
Видимо не знаете.
NSA
Но они же платформозавсимые типы, а не языкозависимые типы ;)
Fails
Советую вам почитать книгу Бьярне Страуструпа «Дизайн и Эволюция C++», в которой он поясняет, почему он сделал именно так (а также такие вещи вида «почему для доступа к статическим членам данных используется два двоеточия (::) а не точка и другие интересные вещи).
Также хочется сказать, чтобы вы никогда не использовали функцию gets(), которая приводит к повреждению памяти при некорректном вводе (если он будет больше, чем размер области памяти, куда вы пишете). Эту функцию удалили из C++14 и из C11 (см. cppreference: en.cppreference.com/w/c/io/gets).
Enmar Автор
Судя по всему вы не так меня поняли, раз приводите в пример ::
Какая разница :: или .?
Никакой.
А вот когда компилятор молчит когда не надо, это плохо...
justhabrauser
Если компилятор видит, что Вы явно собрались стрелять в свою ногу — да, он молчит.
Он так воспитан — дать человеку выстрелить себе в ногу, если ему это очень нужно.
dlinyj
Скажу за си только, так как с++ не знаю. Си — это язык низкого уровня. И иногда на нём очень удобно делать практически ассемблеровские вещи. При не знании того, что ты делаешь — отстреливаешь себе обе ноги, при знании — делаешь удивительный код,
который мало кто понимает.Прежде чем писать подобный пост, попишите на этих языках лет 10 в крупных конторах, и потом может быть ваш пост будет полезен. Но скорее всего через 10 лет вам даже в голову не придёт писать ахинею.