Как стать автором
Обновить

Комментарии 100

Если Вы не пишете аппаратно-зависимый софт, то этого всего Вы скорее всего не встретите. Стандартные библиотеки написаны максимально безопасным способом, чтобы при обычном использовании не возникало описанных проблем.
А если Вы делаете что-то на уровне битового представления типов, то обычно это делаете под конкретную платформу, либо реализуете это с применением приемов адаптации под другие платформы.
Ну в целом да, всякие 32-битные char'ы и 33-битные int'ы в повседневной жизни встретить довольно сложно, но про всякие подводные камни типа «перемножение двух беззнаковых типов размером меньше чем int способно привести к signed overflow из-за integer promotion» знать все-таки нужно, такое вполне может встретиться в реальной жизни.
Все эти неприятности возникают при переносе кода, особенно с какого-нить старого 16-ти битного арма на 64-х битный.
А что за 16-битный ARM? Может 26-битный? Но там вроде никаких особых страстей всё равно не было.
то этого всего Вы скорее всего не встретите

Ну некоторые моменты никак библиотекой не спрячешь. Например, в результате вызова std::abs можно получить отрицательное число на любой платформе. А уж сколько было уязвимостей из-за целочисленное переполнения и сколько еще будет.

Например, в результате вызова std::abs можно получить отрицательное число на любой платформе
Пограничный случай с -128/127 (и другие пределы) или что-то более интересное?
Хм, что может быть Вам интересно, как раз для std::abs(INT8_MIN) и std::abs(INT16_MIN) не просто так выявить их отрицательную сущность, ни в на печати, ни в сравнениях. Ввиду изложенного в статье «std::cout << std::abs(INT8_MIN)» напечатает «128»! А вот «std::cout << std::abs(INT64_MIN)» напечатает отрицательное число.
Ну то есть речь идёт всё же ровно об одном пограничном случае м xxx_MIN. ОК.
Проблема не столько в пограничном случае, хотя кто-то может быть искренне уверен в assert(std::abs(zz) >= 0), а это зависит.

Сколько ещё в нескольких тонкостях. Скажем, Вы понимаете, что и почему напечатает: ` std::cout << std::abs(INT8_MIN) << std::abs(INT16_MIN) << std::abs(INT32_MIN) << std::abs(INT64_MIN) '?

Я, к своему стыду, абсолютно не понимаю. Можно ткнуть носом, что почитать для понимания?

C++20: 26.8.2 Absolute values
C17: 7.22.6 Integer arithmetic functions
C17: 7.20.2.1 Limits of exact-width integer types
C++20: 6.8.4 Integer conversion rank
C++20: 7.3.6 Integral promotionsп

Первое значение «std::cout << std::abs(INT8_MIN)» — строго полностью определённое поведение, последнее значение «std::cout << std::abs(INT64_MIN)», на подавляющем числе систем, строго неопределённое поведение.
Вы понимаете, что и почему напечатает: ` std::cout << std::abs(INT8_MIN) << std::abs(INT16_MIN) << std::abs(INT32_MIN) << std::abs(INT64_MIN)
Да, понимаю, потому что базовый тип для std::abs — int и первые два значения будут расширены до него.
Но, опять же, эксплуатация ровно одного пограничного случая.
Хм, да, понимаете? А это ничего, что INT8_MIN и INT16_MIN — имеют тип int? А std::abs() перегружен по трём типам аргумента int/long/long long? В C++/C, увы, трудно быть хоть в чём-то полностью уверенным.

А так, да, С++20 ссылается на C17, а в C17 для abs(), labs() и llabs(), прописано, что если результат не может быть представлен, то поведение неопределённое (с отдельным напоминанием за наименьшее целое, в случае, дополнительной арифметики). Ну, а в других функциях могут быть свои заморочки.
Хм, да, понимаете? А это ничего, что INT8_MIN и INT16_MIN — имеют тип int? А std::abs() перегружен по трём типам аргумента int/long/long long?
То есть ровно то, что я и написал. Вы опять обсуждаете ровно один пограничный случай.
Насчёт любой, это ж вряд ли, ещё существуют калькуляторы без дополнительного кода.
А C-то на них существует? Насколько я знаю C поддерживается только на горсточке очень новых калькуляторов и там везде 32-битные процессоры. ARM, MC68000, SH-3
В каком-нибудь военном/ядерном/авиационном/космическом вычислителе можно нарваться на арифметику с поглощением (а ля x86/x64 SIMD/OpenCL) или на модульную арифметику для быстрых вариантов целых типов.

Или, как вариант, какая-нибудь военная/ядерная/авиационная/космическая libc выдаст иной вариант неопределённого поведения, отличный от типичного std::abs(INT64_MIN) == llabs(INT64_MIN) == INT64_MIN
А, вы сюжет для фантастического романа обсуждаете, не реальный мир.

Я, в принципе, не против фантастики, но предпочитаю её обсуждать в отдельных темах.
В стандарте написано — неопределённое поведение, а неопределённое поведение это не только отрицательное значение std:abs(). Если бы всё было бы так однозначно, в стандарте бы так и написали: llabs(INT64_MIN) == INT64_MIN. С арифметикой — аналогично.

Я вполне себе могу представить компилятор, который операции с int16_t не векторизует, т.к. стандарт требует для них дополнительной арифметики, а int_fast16_t векторизует.
Причём тут std:abs, извините? Представление чисел важно не только для std:abs, но и для банального a = b;. Тут нет неопределённого поведения при любом целочисленном a и любом целочисленном b.
Ну, началось же с: «Ну некоторые моменты никак библиотекой не спрячешь. Например, в результате вызова std::abs можно получить отрицательное число на любой платформе.» от apro

Так же, разрешите напомнить, что в стандарте C17, на который ссылается C++20, указано «The abs, labs, and llabs functions compute the absolute value of an integer j. If the result cannot be represented, the behavior is undefined

Для некоторых, редких, вариантов реализаций int/long/long long, такая ситуация неопределённого поведения невозможна, так же как для некоторых, редких, компиляторов/библиотек это неопределённое поведение может быть нетипичным.

Кроме того, какие фокусы, бывает, выкидывают современные оптимизирующие компиляторы для программ с неопределённым поведением, наверное, не мне Вам рассказывать.
std::abs(INT64_MIN) == llabs(INT64_MIN) == INT64_MIN
это вы ещё сравнивать вещёственный числа не пробовали видимо :)
float a = 3.5;
float b = 2.0 + 1.5;
if (a == b) { ... // <-  так сравнивать нельзя
Это почему это нельзя?! Для кого IEEE-754 (ISO/IEC/IEEE 60559) пишут? В данном случае можно, т.к. 1.5, 2.0 и 3.0 имеют точное представление. Конечно, по хорошему надо поставить static_assert на __STDC_IEC_559__/__GCC_IEC_559 и несколько ещё вариантов.

Эх, было время, я длинное целое умножение/деление на плавающем сопроцессоре делал.

Знать как нельзя, это действительно большое достижение, многие даже и не знают, но некоторым бывает интересен ответ на вопрос почему.
Опять рассматриваем частный случай? ОК, даже если так, перефразирую вопрос, почему следующая функция не корректна
bool is_equal( float a, float b ) {
return ( a == b );
}
Конечно, для C/C++ немного странная функция, аргументы float, но зависит от программы.

И нет, и это не частный, случай. В предыдущем тезисе Вы, по-моему, так и не выделили три различные проблемы, а свалили всё в одну кучу.

А здесь, так это вообще множество общих случаев.

Просто к ошибкам округления, как и ко всему остальному, стоит подходить с пониманием. Есть алгоритмы, когда их можно считать случайными. Есть алгоритмы в которых их следует учитывать для правильного выбора констант разложений и т.п. (0.125 — точно, 0.1 — неточно, /10 — точно). Есть алгоритмы, где они «самоуничтожаются» (скажем, Кнут т. 2, s = a + b; e = b — (s — a);). А есть алгоритмы где их вообще не возникает.
Понятно, теоретик. Так и запишем.
Хм, художника всякий обидеть норовит?! Я бы это назвал, практик, вычислитель, вычислительная математика она ж такая. У меня есть все виды перечисленных выше программ, которые работают много много лет.

И да, в юнит-тестах, регрессионных тестах, а так же в тестах самотестирования на объекте, double сравниваются на равенство. Что б поймать за руку какого-нибудь молодого, да раннего, инженера по сборке, если он накосячит. Или сторонний модуль, если он что-то в настройках FPU изменит.
Я бы это назвал, практик, вычислитель, вычислительная математика она ж такая
Математик-теоретик, не программист-практик, уж извините :)
И да, в юнит-тестах, регрессионных тестах, а так же в тестах самотестирования на объекте, double сравниваются на равенство

github.com/google/googletest/blob/master/docs/advanced.md#floating-point-comparison
они ссылаются сюда
randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition
Уверен, что вы читали эту статью или подобную, но ответ:
к ошибкам округления, как и ко всему остальному, стоит подходить с пониманием. Есть алгоритмы, когда их можно считать случайными. Есть алгоритмы в которых их следует учитывать для правильного выбора констант разложений и т.п. Есть алгоритмы, где они «самоуничтожаются». А есть алгоритмы где их вообще не возникает.
как раз и написал математиком-теоретиком, а не программистом-практиком, или прагматиком, если хотите.
Я не хочу никого обидеть, но это мурзилки для тех, кто прогуливал уроки в школе. Которые, к тому же, желают вместо понимания IEEE-754, С17, С++20, неких магических ритуалов.

Грубо говоря,
for(i = 0; i < sqrt(n); i++)
    a[i*i] = 1.;

Совершенно не требует «улучшайзинга». ;) Если в контексте уместно, то вместо «i < sqrt(n)» столь же корректно написать «i != sqrt(n)» ;) Если n < 252, конечно, но компьютеры с памятью больше 255, есть только у китайцев. ;) И таких примеров вагон и маленькая тележка.

Сравнивать на равенство нужно, например, чтобы определить, надо ли сериализить значение или оно дефолтное.

вещественные числа сравнивать на равенство нельзя, в общем случае.
(a — b) < epsion
Хм, а ещё станцевать танец отпугивания злых духов и трижды перекреститься. (Да, std::abs() ещё ж забыли...) И получить, в результате, некорректный код.

Заметьте: «или оно дефолтное»!

Нет, ну если процессорного времени вагон и маленькая тележка? Почему нет? Но Роскосмос, НАСА, как и Илон Маск, лично, не одобрят.
В начале перечисляются стандартные типы
char: минимум 8 бит в ширину;
short: минимум 16 бит и при этом не меньше char;
int: минимум 16 бит и при этом не меньше short;
long: минимум 32 бит и при этом не меньше int;
long long: минимум 64 бит и при этом не меньше long.
но ни слова про уже, практически столь же стандартные (u)int8_t/(u)int_16_t и т.д.
И позже, внезапно:
uint8_t len = (...);
for (uint8_t i = 0; i < len; i++) { ... }
что? откуда взялось uint8_t если до этого про этот тип ни слова?
Пусть uint32_t = unsigned char, и int равен 33-битам.
Откуда взялся теоретик ни когда не слышавший о моделях ILP32, LLP64, LP64? Экзотика, конечно встречается, но и тогда есть определённый ряд соглашений, иначе ни одну библиотеку под эту платформу портировать не удастся, а, скажем, писать реализацию TLS 1.3/1.4 самостоятельно вряд ли кто захочет.
В stdint.h определена конкретная ширина типов: uint8_t, int8_t, 16, 32 и 64. Будьте внимательны к операциям, подразумевающим продвижение типов. Например, uint8_t + uint8_t даст int (со знаком и шириной не менее 16 бит), а не uint8_t, как можно было предположить.


Вы не очень внимательно читаете
Это всё же не перечисление типов, это именно рояль в кустах ещё и с кривым определением
This header defines a set of integral type aliases with specific width requirements

uint8_t может быть не определён, например, на процессоре с минимальным адресуемым словом в 16 бит. Но тип, скажем, uint_fast8_t всегда есть

Хороший обзор. Но для полноты стоило бы упомянуть нюансы little-endian vs big-endian.
Насколько я понял автора, посыл в том, что C — это не кроссплатформенный ассемблер с единым синтаксисом. Тут полностью согласен. А реально с уверенностью рассчитывать можно только на то, что один бит всегда принимает значения «0» и «1» но это это не точно
Преобразование указателя в int и обратно в указатель происходит без потерь
На x64, ожидаемо указатели 64 битные (а int 32 битный).
Стандарт C допускает такие платформы, где указатель не может быть без потерь преобразован ни в один целочисленный тип (типы вида uintptr_t являются опциональными и могут отсутствовать). Думаю, автор привёл int для примера и экономии места.
На x64 свет клином не сошёлся
Присутствие signed и unsigned версии каждого целочисленного типа удваивает количество доступных вариантов

Было дело, я сначала плевался на Java: как из-за отсутствия unsigned переносить код с с/с++? Потом восхищался C#: «ну вот там же сделали, всё для людей!»
Потом понял: те операции, для которых (тогда мне было) это важно — они вообще не про арифметику были, а про AND, OR, XOR, где вообще понятия «знаковости» нет. Но в коде, который довелось переносить — почему-то через "+" и "-" многое было сделано.
Спасибо! Очень интересно было узнать про то что char бывает разного размера. Мне char больше чем 8 бит никогда не встречался.
Посмотрите на TMS320C3x/4x или 1867ВЦ6Ф. Там все типы 32 бита (за небольшим исключением). :)
надо же, у TMS320C3x/4x даже double 40 бит
Так ведь char уже может быть utf (а там как раз до 32 бит)?
Имеется в виду, что мне не встречалось такое устройство, чтобы в <limits.h> написанном под него значение CHAR_BIT было равно любому числу кроме 8. Так уж сложилось, что слово character в стандарте языка C и character в стандартах описывающих UTF — обозначает совсем разные вещи.

Старые 36-битные мейнфреймы имели char 9 бит

А как int может быть шириной 33 бита, если ранее вы пишете


Битовая ширина должна быть кратна 2.

?

Я полагаю, что автор исходного текста хотел таким образом привлечь внимание к проблеме. Но просто сам себе противоречит в размере. Примите в данном примере ширину 34-м битам, смысл останется тот же.
Судя по string он и есть.
Глаз-алмаз, я прошляпил. Но судя по остальному коду, это писал сишник на Си, просто он освоил немного стандартной библиотеки С++ :) Особенно объявления всех переменных без иницализации вверху скоупа радуют мой глаз.
Слава богу вверху скоупа даже в C объявлять переменные больше не нужно. Правда под Windows придётся взять довольно новую Visual Studio. Microsoft с переходом с C89 на более новые стандарты чуть-чуть затянул.

Но перешли же?
До сих пор впадаю в ступор, когда вижу код наподобие
unsigned long long = -1;
Я ведь правильно понимаю, что это эквивалентно
unsigned long long = -1ull;
?
Тоесть если к результирующему значению прибавить 1, то получится 0?
В статье об этом сказано, но не очень понятно
я думаю, что в случае с unsigned long long = -1 сначала -1 продвигается до signed int и дальше преобразовывается к типу unsigned long long для этого скорее всего будет прибавлен LLONG_MAX + 1. Однако, это не эквивалентно unsigned long long = -1ull. Так как в этом случае правая часть уже имеет тип unsigned long long.
unsigned long long = -1;
Просто общепринятый трюк с переполнением для получения 0xFFFF…
Я ведь правильно понимаю, что это эквивалентно
unsigned long long = -1ull;
Результат — да, преобразования — нет.

Это определено стандартом. signed-to-unsigned implicit conversion.


Although the definition uses -1, the value of unsigned type is the largest positive value it can hold, due to signed-to-unsigned implicit conversion. This is a portable way to specify the largest value of any unsigned type.

Кстати, автор в 4 пункте раздела "Заблуждения" остерегает использовать этот трюк.
Ни разу ни плюсовик, но часто сталкиваюсь с тем что приходится спорить даже с "продвинутыми" плюсовиками.
Из недавнего, спор о длине(размере) char. Ни один из трёх не знал что стандарт не гарантирует длину в 8 бит. Прямо как откровение. И так с каждым положением.
Могу сделать совет начинающим вайтишникам — не начинайте учить плюсы и джаваскрипт если вам дорого ваше психическое здоровье, и вы хотите быть уверенным до конца хоть в чём то.


ПС А ведь в статье слегка освещены проблемы только с целочисленными типами. А сколько там граблей с вещественными числами — мама не горюй...

Ни один из трёх не знал что стандарт не гарантирует длину в 8 бит.
Тоже мне проблема. Пишем:
[[maybe_unused]] std::int8_t we_need_8bit_char;

Всё, стандарт резко загарантировал длину 8 бит.
Может быть минусующие объяснят, что им не по нраву?

Стандарт действительно гарантирует 8битный char, на платформах, где вышеописанный код скомпилируется.

И, соотвественно, любой код, в котором есть хотя бы одна переменная типа std::int8_t — тоже может полагаться на 8-битный char.

В чём проблема-то?
Проблема, думаю, в разном понимании «гарантирует длину в 8 бит». Ваш код блокирует компиляцию на некоторых, редких, платформах. А товарищ, вероятно, имел ввиду, что его собеседники испытывают затруднения при написании работающего переносимого кода и для этих платформ тоже.
Эх, это вы еще не перечислили проблемы с системами построенными не на двоично-дополнительном коде.
Вот скажем «классический пример»:
switch (-1&3)
{ case 1: /* ... */ break;
case 2: /* ... */ break;
case 3: /* ... */ break;
}

когда сработают ветки case 1 и case 2?
Только вот таких систем, похоже, в мире просто не осталось. Еще в 2012 году на StackOverflow на вопрос а остались ли на практике такие системы смогли лишь найти один чрезвычайно частный случай со своим собственным компилятором и отметить, что системы не на дополнительном коде скорее всего успели выйти из активного употребления еще до стандартизации С.

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

stackoverflow.com/questions/12276957/are-there-any-non-twos-complement-implementations-of-c
Надеюсь всё-таки P0907 примут и проблема останется в прошлом.

Да, именно архитектур почти не осталось. Например: руководство по С для unisys 2200.

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

Ещё один момент забыт. Иногда это бывает важно — может не так часто, но в любом случае знать стоит. signed int и int — это один и тот же тип, signed long и long — тоже и т. д., но вот char, signed char и unsigned char — это три разных типа.

char, signed char и unsigned char — это три разных типа.
Лет 10 назад и (un)signed bool работало, и сейчас может сработать для чистого Си, где bool объявлен как какой-нибудь char или int :)
в «чистом Си» до C99 типа bool вообще не было, поэтому до сих пор в легаси коде С89/90 встречается определение bool как int и, соответственно, занятные моменты, связанные с этим. В MSVC до версии 2008 unsigned bool был валидным типом. Про GCC не помню, там всё достаточно хорошо было организовано уже очень давно.
типа bool и после C99 нет. bool это просто макрос который соответствует типу _Bool. Если компилятор тип _Bool не определяет, то он будет определён как int.
Если говорить о легаси до C99, то там bool ваще как что угодно может быть определён. я и как enum встречал.
> for (unsigned int i = len; i > 0; i--) {
> process(array[i — 1]);
> }

for (unsigned int i = len; i-- > 0;) {
process(array[i]);
}

Самое забавное, что комитет только к C++20 выкатил ssize() и только к C++23 родил суффиксы для ptrdiff_t и size_t (и наотрез отказывается вводить ssize_t).
Скоро никаких static_cast'ов, надо только подождать...

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

Недавно я делал 96-битную арифметику на стандартном Си. Только сложение и вычитание. Сложение получилось быстро, с вычитанием пришлось долго повозиться. Затраты времени на это оказались в десятки раз выше, чем на написание соответствующего кода на ассемблере.
Присутствие signed и unsigned версии каждого целочисленного типа удваивает количество доступных вариантов. Это создает дополнительную умственную нагрузку, которая не особо оправдывается, так как типы со знаком способны выполнять практически все те же функции, что и беззнаковые.


Если все корректно работает, то умственной нагрузки нет. Ведь правило простое: если число может быть отрицательным, то signed. Просто, не правда ли? Проблема в другом.

Главное, чтобы это правило в обратную сторону не работало. Если число не может быть отрицательным, то его НЕ нужно делать unsigned.
Правило скорее такое: используйте signed везде, кроме мест, где нужно играться с битами.

Ведь правило простое: если число может быть отрицательным, то signed. Просто, не правда ли?
Все так, если бы этому правилу всегда следовали. А то ведь time_t, например, знаковый, с той целью, чтобы функции, возвращающие time_t значения, могли вернуть -1 в случае ошибки. А был бы он беззнаковым, могли бы не париться про 2038 год.
современный с++ дегенерат-вырожденец, хотите «умные» указатели, не е… те людям мозги, возьмите c#, java и т.п. А если используете variadic темплейты, лучше ждите технологическую сингулярность.
Как Вы жестко с C++, мне кажется, это уж перебор
Почему?
Умные указатели — следствие отсутствия уборщика мусора
variadic темплейты — строгой типизации
тут только 1с или basic может помочь

Почему не Rust?

Дарован был людям Раст, нет, будем сотни страниц со списками особенностей приведения и неопределённого поведения держать в голове. А потом в Гугл заявляют, что у них 70% ошибок из-за ошибок в работе с памятью. Если ещё переполнение и все эти UB добавить, то смело можно говорить о 90%.

Боюсь, лет через 5 С++ будет эдаким Паскалем/Делфи, который по инерции вроде ещё изучается и даже используется в старых проектах, но новых практически нет.
Ну, с растом тоже многое нужно держать в голове, например то, что len() в итераторе может теоретически вернуть что угодно, а если этого не держать в голове, то могут случиться ошибки в работе с памятью внезапно. Никакой раст не спасет от необходимости думать при написании кода и внимательно читать документацию, а иначе проблемы неизбежны — хоть с памятью, хоть с чем.
Это относится, во-первых, к сторонней библиотеке, во-вторых, это ошибка, а не стандартизированное поведение, ну и под него уже подстелили соломку. Ну и вон целый комитет есть, который следит за безпастностью сторонних либ. Даже не знал про него.

Разумеется, и там можно выстрелить в ногу, если постараться, но там компилятор, хоть и тормозной, даёт куда больше гарантий, а всё или почти всё, что даёт UB, разрешено только в unsafe-блоках, и это очень небольшая часть языка и стандартной библиотеки, нужная для нетривиальных случаев.
Это типичная ошибка, связанная с тем, что что-то, написанное в примечании на некоей странице, на которую ведет не слишком приметная ссылка с другой страницы с описанием функции len(), разработчик сторонней библиотеки (типичный средний разработчик, который пишет или будет писать софт на расте) не держал в голове. «Все как всегда, и лучше будет едва ли» ©. А если писателей на расте станет достаточно много, то количество подобных ошибок будет увеличиваться в геометрической прогрессии.

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

Верно. Но ошибка в unsafe-блоке — это тоже ошибка. В расте ряд вещей сделан «более по уму», чем в C/C++, и так и должно быть — он не вынужден тянуть на себе бремя совместимости длиной в полсотни лет, но он от этого не становится какой-то серебряной пулей. По-прежнему нужно держать многое в голове, особенно в случае «нетривиальных случаев», да.
Ошибку в unsafe легко отловить — она локализована, а остальной код проверяет компилятор, если, конечно не писать в стиле «unsafe fn main()». Если, как в Си, при разыменовании любой указатель может потенциально быть невалидным, это уже совсем другое дело. Понятно, что в современном С++ сейчас нет особенной нужды на каждом шагу жонглировать указателями, но частных случаев, неявных приведений, методов по умолчанию, и т. п., остаётся до ужаса много. Вон даже в банальной арифметике столько разных нюансов. Тут где-то на Хабре была статья бывшего плюсовика со стажем чуть ли не в 20 лет, который подробно описывал свои мысли по этому поводу.
Ошибку в unsafe легко отловить

Относительно легко. Но думать при этом всё-таки надо. Что бы вам такое для примера привести… Например, есть такой трейт — Zero. Имплементирующий его код обязан соблюсти закон, что прибавление нуля не меняет значение, это прописано в документации. Вопрос: можете ли вы в своём unsafe коде рассчитывать на этот закон для неизвестного типа? Правильный ответ: нет.

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

Если бы меня спросили, что в первую очередь улучшить в С/C++, то первые в расстрельном списке это неявные преобразования между различными целыми, а также между целыми и числами с плавающей точкой…

Тяжкое бремя совместимости висит над ним. Ну так хотя бы добавили прагму для отключения…
Добавлю ещё один миф: int принимает разрядность целевой вычислительной архитектуры. Это неверно, потому что для 8-битной архитектуры AVR, ширина int равна 16 битам. Но это скорее сделали так, потому что у крупных ядер AVR есть инструкции, типа movw и регистровые пары, используемые в качестве указателей.
Но это скорее сделали так, потому что у крупных ядер AVR есть инструкции, типа movw и регистровые пары, используемые в качестве указателей.
И всё это приводит к тому, что понятие «разрядность вычислительной архитектуры» становится понятием не шибко определённым.

Мне даже никто так и не смог объяснить, почему Z80 — 8-битный, 8086 — 16-битный, а Pentium4 — 32-битный. Потому что по размерам регистров у вас Z80 получается 16-битным (там IX и IY есть и операции с ними), а по размеру ALU у вас Pentium4 окажется 16-битным (там ALU работает с половинками регистров на удвоенной частоте).
почему Z80 — 8-битный, 8086 — 16-битный, а Pentium4 — 32-битный. Потому что по размерам регистров у вас Z80 получается 16-битным
Шина адресов да, 16 бит, но данные всё равно читались по 8 бит. Такая же история и с i8088/КР580ВМ, где шина адреса тоде была 16 битной, а данные всё равно читались по 8 бит.
По этой причине процессоры и были 8-битными, первый полноценно 32-битный (из известных) считается i386, где все регистры были сделаны 32-битными, у его предшественника — i286, регисты шины адреса были 24-битными, а данных — 16, при этом было несколько регистров по 48 бит, 40 и даже 64.
Шина адресов да, 16 бит, но данные всё равно читались по 8 бит. Такая же история и с i8088/КР580ВМ, где шина адреса тоде была 16 битной, а данные всё равно читались по 8 бит.
Вот только Z80 — считается вершиной 8-битных процессоров, а i8088/КР580ВМ — считаются 16-битными.

По этой причине процессоры и были 8-битными, первый полноценно 32-битный (из известных) считается i386
Что значит «полноценно 32-битный», извините? А 68k куда дели?

Некоторые, правда, говорят, что первенец, 68000 — типа 32/16 битный, так как ALU у него 16 битное. Но вот Prescott 64/30-битным не называют. Впрочем 68020 всё равно на год раньше 80386го появился.

при этом было несколько регистров по 48 бит, 40 и даже 64.
Современные процессоры имеют регистры аж до 512 бит. Но 512-битными их никто не называет.

В общем битность процессора это дело такое, зависящее от документации, а не от реальной архитектуры. Потому с ней размер int и не совпадает.
i8088/КР580ВМ — считаются 16-битными.
Эмммм… нет
КР580ВМ80А — 8-разрядный микропроцессор.
ru.wikipedia.org/wiki/%D0%9A%D0%A0580%D0%92%D0%9C80%D0%90
С i8088 это я ошибся, КР580 был клоном i8080
Что значит «полноценно 32-битный», извините? А 68k куда дели?
А вы ремарку прочитали или сразу спорить?
Ни куда не дел, так как оригинальный 68к имел 16-битные разряды данных, о чём прямо даже в вики написано
RU: из-за чего иногда процессор описывается как имеющий смешанную битность 16/32
EN: Generation one (internally 16/32-bit...
Полноценный 32-битный 68020 вышел конечно раньше i386, на год, примерно, на нём даже был выполнен Apple II, если не ошибаюсь.
В общем битность процессора это дело такое, зависящее от документации, а не от реальной архитектуры
Об этом и речь, но ведь оригинальный вопрос был, почему Z80 не 16-битный, потому что набор команд и шина данных 8-битные, а размер регистров не определяет битность процессора.
С i8088 это я ошибся, КР580 был клоном i8080
А я только на i8088 и глянул, не обратил внимания, что вы не про КР580ВМ88.

Об этом и речь, но ведь оригинальный вопрос был, почему Z80 не 16-битный, потому что набор команд и шина данных 8-битные, а размер регистров не определяет битность процессора.
Вот только в наоборе команд есть и 8-битные и 16-битные регистры, а шина данных такая же 8-битная, как и у 8088.

И у того же «смешанного» 68000 регистры и система команд таки 32-битная.

а размер регистров не определяет битность процессора.
А что её определяет? Вот почему у вас 8088й резко стал 16-битным, аж даже извиняться и «отыгрывать» пришлось?

И почему послений, доживший до сегодня, потомок Z80, имея 24-битные регистры и ALU считается, тем не менее, 8-битным — из-за архитекуры, типа, а 68000, имеющий 32-битную архитектуру — не считается 32-битным? Хотя совместимый с ним 68020 (гораздо более совместимый, чем 80386: там старые программы работают в особом, 16-битном, режиме) — полноценно 32-битный?
вы не про КР580ВМ88
я даже специально загуглил и могу ответить только цитатой с одного из форумов
Очень интересно, учитывая, что КР580ВМ88 не существует, а КР580ВМ80 — это i8080, с i8086/88 не совместимый.


Вот почему у вас 8088й резко стал 16-битным, аж даже извиняться и «отыгрывать» пришлось?

потому как i8088 это переделка i8086, 16-битного процессора под 8-битные микросхемы памяти, а извинялся я за опечатку, вместо 8080 я напечатал 8088.
а шина данных такая же 8-битная, как и у 8088
Тут да, но оперировал 8088 16-битными данными, а даунгрейд был сделан по техническим причинам
The main difference is that there are only eight data lines instead of the 8086's 16 lines. All of the other pins of the device perform the same function as they do with the 8086 with two exceptions. First, pin 34 is no longer BHE (this is the high-order byte select on the 8086—the 8088 does not have a high-order byte on its eight-bit data bus).

Ну то есть 8088 читал побайтно даже 16-битные данные (и писал так же), так как 8-битных чипов память наделали столько, что нужно было куда-то их реализовывать.
а 68000, имеющий 32-битную архитектуру — не считается 32-битным? Хотя совместимый с ним 68020 (гораздо более совместимый, чем 80386: там старые программы работают в особом, 16-битном, режиме) — полноценно 32-битный?
Если хотите спорить ради спора, то хотя бы читайте первоисточники, хоть какие-нибудь.
The Motorola 68000…

The design implements a 32-bit instruction set, with 32-bit registers and a 16-bit internal data bus. The address bus is 24-bits and does not use memory segmentation, which made it popular with programmers. Internally, it uses a 16-bit data arithmetic logic unit (ALU) and two more 16-bit ALUs used mostly for addresses, and has a 16-bit external data bus. For this reason, Motorola termed it a 16/32-bit processor.

The 68020 added many improvements over the 68010 including a 32-bit arithmetic logic unit (ALU), 32-bit external data and address buses, extra instructions and additional addressing modes.
На это стоит ответить отдельно
(гораздо более совместимый, чем 80386: там старые программы работают в особом, 16-битном, режиме)
Ну то есть появление в i386 принципиально иных режимов, как то, страничное управление памятью, защищённый режим работы процессора, который и заставил делать целый слой совместимости со старыми процессорами — виртуальный режим, это фигня и её можно сравнивать с эволюцией линейки 68k?
Ну то есть, ещё раз, 68020 и i386 в реальном режиме как бы да, сравнимо, но i386 в защищённом режиме это как бы совершенно отдельная история.

Для 68020 и 68000 нет никакого 'реального режима'. Они всегда работают с полноценными 32-битными регистрами и полноценной 32-битной адресацией (из которой только 24 бита адреса вылазят наружу для 68000, но уже все 32 бита для 68020). В противовес 8086 с ужасной 16-битной адресацией через сегменты, навёрнутом на основе этого в 80286 защищённом режиме и наконец расширении всего этого унылого безобразия до 32 бит с добавкой виртуальной страничной памяти в 80386.


Взамен всей этой кучи бессмысленной ерунды в 68020 просто добавили страничную MMU (внешним чипом, с 68030 MMU уже на кристалле процессора).

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