Comments 119
Требование поддержки JSON в стандартной библиотеке — апофеоз глупости для низкоуровневого языка. Обращение к символам юникодной строки по индексу, равно как определение ее длины по размеру массива — это позор. Наезд на sizeof() — это вообще демонстрация полного непонимания работы платформозависимых типов. Пожалуйста, учите матчасть перед написанием поста.
По теме. Оба языка достаточно старые, с кучей легаси. Тот факт, что они живы и активны до сих пор, говорит о том, что для этого есть веские причины. Работа с юникодом из коробки — для этих языков далеко не самое важное. Если новый проект в 2019 году стартует на С/С++ — это как минимум понимание бизнес-целей, рисков и стоимости разработки. Я сам отношусь к людям, которые не против видеть Rust в списке технологий разработки вместо C, но могу сказать, что вышеописанным вы оказываете Rust медвежью услугу. Равно как и то, что эти два мамонта из своих ниш выбить крайне маловероятно, и в принципе не очень разумная затея.
Наезд на sizeof() — это вообще демонстрация полного непонимания работы платформозависимых типов.
Я понимаю, что размер int разный от платформы к платформе.
Но странно, что в си компиляторе это воспринимается так, а в ++ компиляторе по-другому.
Равно как и то, что эти два мамонта из своих ниш выбить крайне маловероятно
Здесь я с Вами согласен.
Ядро юникса и винды например на сях.
странно, что в си компиляторе это воспринимается так, а в ++ компиляторе по-другому.
Потому что это два разных языка.
Также хочется сказать, чтобы вы никогда не использовали функцию gets(), которая приводит к повреждению памяти при некорректном вводе (если он будет больше, чем размер области памяти, куда вы пишете). Эту функцию удалили из C++14 и из C11 (см. cppreference: en.cppreference.com/w/c/io/gets).
Судя по всему вы не так меня поняли, раз приводите в пример ::
Какая разница :: или .?
Никакой.
А вот когда компилятор молчит когда не надо, это плохо...
Он так воспитан — дать человеку выстрелить себе в ногу, если ему это очень нужно.
Прежде чем писать подобный пост, попишите на этих языках лет 10 в крупных конторах, и потом может быть ваш пост будет полезен. Но скорее всего через 10 лет вам даже в голову не придёт писать ахинею.
int 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)
Компилятор провёл лёгкую оптимизацию за нерадивым программистом и вместо сложения числа с самим собой просто умножил его на два.
кто вы такой, сколько у вас лет опыта C/C++? — ваше мнение релевантно?
не первая уже статья такого толка, вы просто за компанию решили бросить на вентилятор чтоли?
так понимаю очередное проталкивание Rust.
я вот, например, после таких статей начинаю от него воротить нос, и желание пропадает даже смотреть что там у вас творится в Rust.
Просто указываю на недостатки с/с++, т.к они немного достали самого)
Настолько топорное проталкивание? Сомневаюсь.
String^ MyString3 = «Hello, world!»; //попробуйте скомпилировать в GCC
А вы в курсе, что это C++/CLI? Ну т.е. это полу-управляемый C++, живущий с .NET? И что ваш
String^
это скорее всего дотнетовский System.String
(когда-то давно я на этом даже что-то писал, но могу ошибаться)? Более того, у VC++
есть свои инструменты для работы со строками, как часть winapi, wchar
и tchar
, первый из которых 16-битовый тип, а второй определяется в зависимости от определенного символа #ifdef UNICODE
(отсюда).Нельзя просто взять любой c++ код и быть уверенным, что он во всех компиляторах будет работать и при этом одинаково
Откроем вашу любимую 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.
А во-вторых запустим программу:
$ ./a.out
Привет: привет!
Вывод: привет!
Я понимаю, что это стандартом не гарантировано, но прочитав текст, создается впечатление, что автор твердо уверен, что крякозябры будут у всех и всегда («Откроем вашу любимую IDE»), а это не так.
Более того, вот так, как у меня, будет и на linux (если локаль utf8, что обычно именно так) и на macOS и еще в куче каких осей.
Где будут проблемы с гарантией — это в Windows. Но проблемы там будут не из за поддержки юникода, проблемы там были всегда потому, что гуйны текстовы редакторы, как и вся система (до юникодизации) использовала cp1251 кодировку (если локаль русская), а виндовозная консоль — кодировку DOS866. Итого, набранную строку в кодировке cp1251 пытаемся вывести в консоль, которая воспринимает эту строку как DOS866, в итоге — крякозябры.
Никаких юникодов еще нигде не было, а проблемы эти уже были. В виндах.
Но все равно неприятно, что такое происходит, пусть и только в винде.
Поэтому критика получается слабой и не убедительной.
Конкретнее пожалуйста.
Вы говорите так работает ос.
А я вам, что компилятор си/с++ мог бы не позволять так просто выстрелить в ногу.
Проблема в данном конкретном случае не в компиляторе, а в программисте, при незначительном вкладе ОС, использующей одновременно три кодировки (а иногда и больше) в разных местах.
Если вы выводите строку в одной кодировке в системе, которая работает с другой кодировкой, то получите ровно ту же проблему абсолютно на любом языке программирования.
Причем надеяться на автоматическое перекодирование (типа у нас язык умный, он может) не приходится, поскольку далеко не всегда возможно корректное преобразование между кодировками (например, попробуйте перекодировать кириллицу в кодировке 1251 или даже UTF-16 в кодировку 1252).
Как минимум прочитайте про кодировки в различных ОС. Прочитайте про разницу с и с++.
Почему компилятор «стреляет» в ногу, так это потому что вы не умеете писать. Используйте другие языки. Кстати, бывают компиляторы с защитой. dihalt у себя на сайте давал пример.
А стандартная библиотека — да, местами кривовата и недостаточно полна. Но это не потому, что язык С++ чем-то плох, а потому, что язык не навязывает какую-то одну реализацию.
И да, язык С — это уже давным-давно совсем другой язык. То, что он немножечко совместим с С++ на уровне вызовов и имеет похожий синтаксис, еще не делает его подмножеством С++.
Именно об этом и писал valexey.
Только не говорите, что это фича, я Вас прошу.
В том же расте или в любом другом более высокоуровневым языке такого не будет.
struct foo{
unsigned char low[128];
unsigned char high[128];
};
И запись в foo.low[] с явным выходом за его границы — фича. Я в зависимости от старшего бита индекса пишу в один или в другой массивы. БЕЗ ветвлений, сравнения битов и тому подобного.
Встречал еще веселее в продуктах IBM (в заголовочных файлах библиотек):
что-то похожее на такое:
struct foo{
int headerField1;
int headerField2;
unsigned char data[0];
// тут память подводит, может быть и [1],
// но память все равно рисует [0]
};
То есть, как минимум, для начала непонятно, куда именно вы там пишете: никто не мешает реализации вставить произвольный padding между low и high.
А как в этих условиях работает #pragma pack (1)?
Насколько понимаю, она в теории полностью снимает вопрос с "произвольным padding" (по крайней мере в жизни не встречал примеров обратного).
Проблема скорее в том, что существует масса старых учебных материалов, в которых новичкам пишут использовать обычные массивы вместо std::array (ex boost::array).
Это называется legacy)
И это баг
Точно так же с неопределенным поведением. Это цена производительности во многом.
Проверка в рантайме в плюсах итак включается в дебаг сборке или принудительно в релизе.
Либо, опять-таки статический анализ.
Хотя, теоретически, std::array во время компиляции может ругаться.
clang выдает предупреждения на выход за границы массива в приведенном примере в статье.
Возможность указать индекс который лежит за пределами блока памяти разрешает вам использовать этот блок в стандартных алгоритмах.
// Я бы так писать не стал
// но возможность есть, на случай например
// использования сторонней библиотеки.
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 еще не изучал подробно.
В gcc и clang есть тип __int128, в то время как в Visual Studio его нет, потому что он не является стандартом.
А у раста вообще НИЧЕГО стандартом не является, так как стандарта на Rust НЕТ. Хотя, быть может я ошибаюсь, и у раста есть ISO стандарт? Можно узнать его номер?
И еще — у раста ровно один компилятор. Понятно что сам с собой он, в основном, совметим. Хотя, насколько я помню, если использовать только стабильные фичи раста, то половина библиотек просто не соберется, так как они юзают «нестандартные», то есть, не стабильные расширизмы компилятора и языка. И это при одном единственном компиляторе раста!
Что же будет, когда у раста появится хотя бы три независимые реализации?
Но 128 битные числа будут гарантировано в кажой раст реализации)
Стандарт раста это доки на их сайте.
Все что там написано будет работать в любом нормальном компиляторе раста.
Еще раз — стандарта на Rust нет. Вообще, в мире довольно мало языков на которые есть живой актуальный стандарт. Могу назвать всего 5 штук: Си, С++, Ада, Фортран, Кобол. Все эти языки максимально дотошно описаны и стандартизированы. Стандарты на все эти языки регулярно обновляются.
Может какие-нибудь языки забыл, поправьте если вдруг еще какой-то язык имеет актуальный ISO стандарт.
На самом деле их немало — https://en.wikipedia.org/wiki/Category:Programming_languages_with_an_ISO_standard
Даже на Ruby есть ISO/IEC 30170
У ECMAScript'а, я смотрю, есть, а вот у Модулы-2, нет. У пролога — тоже нет. Ну и далее, по списку.
А вот про руби не знал, спасибо. Если про ECMAScript я просто забыл, то про руби я вообще не был в курсе, что там стандарт ISO есть.
Ещё вроде SQL на доске почёта должен быть.
Вы знаете, я как постоянный крестоносец смотрю на этот стандарт каждые три года, и как-то грустно становится… Зато добавили эллиптические кривые и космолёт...
А какие проблемы С и С++ знаете Вы?Самая большая проблема C++ — его постоянно критикуют те, кто в нем не разбирается.
но до сих пор нет поддержки юникода(!).
Просто указываю на недостатки с/с++, т.к они немного достали самого)
Пожалуйста, скажите в чем именно я не прав?
Присоединюсь к предыдущим ораторам, в вопросах кроссплатформености, размеров типов, понимания ОС и т.д. и задам только один вопрос (из двух пунктов): Вы с каким юникодом собирались работать или C++ должен за Вас магическим образом догадаться и сделать всю сервисную работу, которые делает «обычный язык» программирования?
То, что предпроцессор ужасен и компилятор компилирует программу которая ломается при
работе, проще говоря runtime error хуже compile error.
черт, скажите мне КАКИЕ ЭТО ОСОБЕННОСТИ?
К примеру:
1. первый вопрос: размер типов. (тупо сколько занимает в памяти и почему именно так)?
2. второй вопрос: что такое String?
3. третий вопрос, вытекающий их первых двух: если юникодов как грязи UTF-8, UTF-16 BE, UTF-16 LE, UTF-32 и т.д. то как С++ должен решить за Вас задачку «Сколько памяти выделить и как кодировать в памяти?» если Вы сами не понимаете что творите?
А в случае со строками — правильным решением было бы вообще отвязать строки от кодировки. То есть «Hello» это строка, а кодировка определяется из настроек проекта и компилятора. А вот если требуется явно указать кодировку — то используются префиксы. Причем для однобайтовых кодировок (которые, несмотря на Unicode, иногда все-же нужны) можно было бы указывать кодировку явно. А если она указана обобщенно (т.е. префикс, означающий «текущая однобайтовая кодировка») то брать указанную в настройках проекта или компилятора. Тогда знаменитый вопрос о крякозябрах при выводе русских строк из консольных программ в винде потерял бы актуальность:)
Примерно аналогично должно быть и с числами. Число 42 — это просто число, а его тип должен выводиться компилятором каждый раз в зависимости от контекста. Это может быть и byte, и int, и unsigned long, и double, и даже какой нибудь mpf_t из GMP.
разработчики стандарта очень боятся нарушить обратную совместимостьв си и в С++ много легаси решений.
Попробуйте просто скачать изображение котика с Вашего любимого сайта с помощью стандартной библиотеки C++.
Это было невозможно до принятия стандарта С++ 17.
Эм… я что-то пропустил? Networking TS же вообще на с++23 отодвинули.
С++ и сеть.
Это 2 ооочень плохо состыкующихся понятия.
#include <boost/asio.hpp> // можно заменить на вашу любимую библиотеку
Не мешайте в одну кучу проблемы с++ и проблемы стандартной библиотеки
На многие проблемы можно получить предупреждение, если компилировать с -Wall -Wextra. Не забываем про статические анализаторы!
Для проверок в рантайме есть -fsanitize (который прекрасно отлавливает выходы за границы массивов) и valgrind.
Не поленитесь изучить опции компилятора, которым вы пользуетесь.
-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
Но согласитесь, круто когда все работает без опций.
Это же основа, не что-то эдакое.
Когда я в IDE компилирую проект, то очень часто я не могу задать опции компилятора.
Но согласитесь, круто когда все работает без опций.
Вам шашечки или ехать? Конечно, было бы здорово, если бы часть из этого была включена по-умолчанию. С другой стороны, очень много проектов тогда начали бы страдать от ложных срабатываний.
Когда я в IDE компилирую проект, то очень часто я не могу задать опции компилятора.
Это что у вас за IDE такая? О_о
Вам шашечки или ехать? Конечно, было бы здорово, если бы часть из этого была включена по-умолчанию. С другой стороны, очень много проектов тогда начали бы страдать от ложных срабатываний.
Ложные срабатывания = плохая реализация.
Еще раз говорю, посмотрите на раст.
Там все лучше с этим.
Там не так просто в ногу выстрелить, ложных срабатываний там нет.
А в ногу выстрелить можно на любом языке, не обольщайтесь. Посмотрите хотя бы на это.
И все же, что у вас за IDE такая? IDE1886 что ли?
Ну знаете ли… Заменить массив символов на указатель на символ и ожидать от такой замены одинакового результата…
Я вообще думаю, что понимание указателей и работы с ними — это мощный инструмент. И люди, пришедшие в C/C++ из других языков пока не поймут указатели — вообще не знают C/C++.
Я говорю, что компилятор должен был сказать, что мол ты фигню мне подсунул.
Вы вполне можете использовать указатель, не выделяя под него память и писать туда что-то. И это не будет ошибкой, если вы, например, направили указатель в буфер видеокарты. Компилятор не может (и не должен) знать о том, что определенная область в адресном пространстве — это мапа в видеопамять и писать туда можно. И Си нужен был именно для этого, это его дефолтовое поведение.
А теперь давайте заменим char string[256] на char* string.
Я говорю, что компилятор должен был сказать, что мол ты фигню мне подсунул.
clang и gcc выдает предупреждение о неинициализированной переменной.
Нужно просто добавить вывод всех предупреждений флагами компилятора.
Если скомпилировать это как с++ проект то условие скорее всего не выполнится.
А если как Си проект, то в таком случае sizeof('a')==sizeof(int).
А если использовать компилятор FORTRAN, то в таком случае этот код вообще не скомпилируется!
Выше уже много написали про конкретные пункты, я просто замечу, что критика любого языка в стиле «мне в языке A не нравится фича Х, но зато в языке B этой проблемы нет» обычно свидетельствует о том, что утверждающий знаком более или менее с языком B, а с языком A — ровно настолько, а чтобы не знать, как решаются в нем конкретные проблемы, а главное, почему они решаются именно так. Ну а это отличный повод начинать холивары.
Вместо холивара изучите лучше оба языка, Rust и С++, они оба вам пригодятся.
Скажите тогда пожалуйста, зачем было так делать?
Затем, что 16...32-х битный процессор не имеет специальных регистров для чаров.
Он хранит их в младшей части обычных регистров размером sizeof(int).
И когда вы загружаете туда чар, вам следует помнить, что при сравнении с другими типами — char(0x32) это абсолютно то же саме, что и int32(0x00000032).
mov AL, byte ptr[ESI + ECX];
содержимое EAX при таком присвоении уничтожается
Нет.
Сплошь и рядом mov AL, byte ptr[ESI + ECX]; cmp AL,0x32 и подобное наблюдаю.
Это на интелах.
А на архитектуре ARM вы такое не наблюдете, ибо там нет побайтного доступа к памяти. Там будет что-то типа «прочитать слово по адресу такому-то, маска 0x00FF0000, сдвинуть вправо на 16 бит».
В случае с С размер int платформенно-зависимый, и для каких-то платформ действительно может равнятся 1, а не привычному нам 4, или 2 некоторое время назад. И это действительно фича, так как int в С определяется физическим размером регистров процессора целевой платформы.
Я даже более вам скажу, не во всех архитектурах char может быть 8 бит. Кстати, тут мне стало дейтвительно интересно. Компиляторы С уществуют для огромного количества самых экзотических аппартных платформ, появлявшихся и исчезавших последние 40 лет. И именно поэтому язык допускает такие вольности, а не потому что код должен быть переносим. А Rust, для какого количества платформ существуют компиляторы и на чем они написаны?
Не, тут Enmar прав относительно Си.
В Си sizeof char литерала равен sizeof(int), хотя sizeof char переменной всё ещё равен единице.
Ну то есть:
char ch = 'Z';
assert(sizeof(ch) == 1);
assert(sizeof('Z') == sizeof(int));
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…
Особенно, если мы говорим о старом типе, в новом string многое было исправлено
и улучшено, но до сих пор нет поддержки юникода(!).
Нет адекватной поддержки только UTF8.
wchar_t (платформенно-зависимый) был всегда.
В C++11 появились char16_t(UTF16) и char32_t(UTF32) и соответствующие им литералы, которые как-бы ЮНИКОД.
Причем кроссплатформенную и самую адекватную строку делают на UTF16.
Да, здесь нам дают выбор. И во многих случаях выбор падет на UTF16 или ASCII (сугубо английский), а не на UTF8 переменного размера.
Вы используете ASCII-строки (однобайтные), а конкретно CP1251 и возмущаетесь на кракозябры.
Это самая банальная вещь, на которой вы и посыпались.
Я тут погуглил, и не могу не заметить, что все же char<x>_t являются не вполне переносимыми из-за LE/BE. https://stackoverflow.com/questions/31433324/char16-t-and-char32-t-endianness. Это, в принципе, описано в стандарте.
Например, с этим можно столкнутся при попытке организовать бинарный протокол между C++ и Java. У последней внутренее бинарное преставление всегда BE (наследие Sun, насколько я понимаю).
Думаю, все знают что в С и в плюсиках куча синтаксического сахара
Что? Какой в Си сахар-то?
Если серьезно, к автору много вопросов.
1. Если это просто желание поделиться радостной эйфорией от первых же граблей, на которые наступил в языке — то явно ресурс выбран неудачно. Лучше публиковать на говнокод ру, например. Там еще можно в пост добавить слово «крестобляди», там вообще такое очень любят.
2. Если это троллинг аудитории хабра, шоб пригорело — то крайне неумелый. Вы пишете такие вещи, от которых фейспалм будет скорее, чем пригорание.
3. Если это не троллинг а честная агитация за Rust — то правильной статьей было бы «вот смотрите какая тупая конструкция на С, а вот на расте пишешь то-то и граблей не собираешь». Статья вышла бы холиварная, но хоть какое-то признание вы бы получили.
И да, всевозможные «плюсики» обороты в тексте совсем не приемлемы для нейтрального принятого стиля на Хабре.
Всего наилучшего.
Насчет Си все намного легче. Под встроенные системы альтернативы нет.
Что мне не нравится в C и С++