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

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

А почему sizeof(int)=4, а не 8 в x64?
зависит от компилятора, но обычно 4 и на x32 и на x64, наверное так решили чтобы при переносе приложений возникало меньше ошибок.
sizeof(int)=4 sizeof(long)=8 sizeof(void*)=8 icc x64
sizeof(int)=4 sizeof(long)=8 sizeof(void*)=8 gcc x64
sizeof(int)=4 sizeof(long)=8 sizeof(void*)=8 clang x64
sizeof(int)=4 sizeof(long)=4 sizeof(void*)=8 msvc x64

А тут интересно чем руководствовались?
Переносимостью. В стандартном C89 есть только 4 размера целых типов: char, short, int и long. В C99 добавился long long. Размеры, по стандарту sizeof(char)sizeof(short)sizeof(int)sizeof(long).

Соответственно если сделать int 64-битным, то на два типа (char и short) будет три размера (8бит, 16бит и 32бит) и «картинка не срастётся».

Так то на большинстве 64-битных платорм int — 32-битный. У x86-64 ещё и инструкции с ним, в среднем, короче.

P.S. Это ещё цветочки, на самом деле. А как вам такое вот, а? Зачем так делать на 64-битной платформе — для меня полная загадка. Представить себе код, который переживёт расширение указателя вдвое, но не long — сложновато, а вот код, автор которого забудет про C99 и long long — как раз наоборот, легко…
Наверное потому, что в win32 api макросы с именем LONG — 32-битные, и ломать программистам мозг тем, что LONG — 32, а long — 64 не хотелось.
Но тем, что в C/C++ long 32-битный, а в C# — 64-битный, они успешно запустили постоянно действующую мозголомку. Стоило ли свеч создавать постоянную проблему после перехода, упрощая переход?
Ну C# это совсем другой мир, с другими задачами. Мне никак это не ломает мозг.
И тут пришёл IntPtr и запрос на native int.
IntPtr только в точках интеграции с неуправляемым кодом. Таких всё меньше, т.к. BCL растёт и всё реже нажо лезть в старые API.

Что такое «запрос на native int», непонятно. В C# это не нужно. Оптимизирует компилятор — хорошо. Не оптимизирует — C# не тот язык, чтобы был смысл считать разницу между Int16/Int32/Int64 в сгенерированном коде.
Любопытный факт
Нет скорее всего стандартизаторы ориентировался, на то что уже наворотили, а не наоборот.
Вообще очень много вопросов к их поставщикам травы. Вот зачем, например, 32bit-ный char надо ограничивать числом 1114111? И что делать со знаковым char в таком случае. Опять UB?
Стандарт ориентирован на общий случай.
А как связаны знаковый char и 32-битный беззнаковый char32_t, на который наложено ограничение? Это совершенно разные типы, ограничение на втором никак не влияет на первый.
Это уже вопрос к Unicode, а не к 32bit-ному char. В Unicode максимальное значение Code Point-а = 0x10ffff (1114111). Так по стандарту, 16 Code Page (страниц) по 65536 символов.
Кстати в ряде 8-битных микроконтроллерах int 16-битный.
int по стандартам (что С, что С++) не может быть меньше 16 бит.
Не от компилятора а от применяемой модели C/C++ ABI.
Причем стандартные(общепринятые) модели в UNIX и WINDOWS отличаются.
Разработчиеи из мира M$ решили, что наиболее просто сделать переход с ILP32 на 64bit архитектуру за счет ABI LLP64 (sizeof(int)=4, sizeof(long)=4 sizeof(void*)=8 sizeof(__int64)=8) это было обусловлено в основном плохим качеством кода и плохим знанием стандартов программистами, пренебрежением стандартов разработчиков VS (например использованием в качестве контейнеров фиксированного размера типов SHORT, INT,LONG etc и неиспользования по назначению size_t,ptrdiff_t, отсутствию stdint.h и inttypes.h можно очень долго перечислять. Где-то начиная с 2015 года большинство недостатков несоответствия стандартам в VS было устранено за что ребятам респект, но к сожалению необходимо еще проделать много работы и C99 для VS не скоро будет достигнут)
В мире UNIX процент квалифицированных программистов был гораздо выше, компиляторы и библиотеки были более близки к стандартам поскольку большинство из них имели значительную кроссплатформенную историю, поэтому стандартной стало C ABI LP64 (sizeof(int)=4, sizeof(long)=8, sizeof(long long)=8, sizeof(void*)=8) т.к общепринято было расширять по степени двойки следующие типы из ряда sizeof(char)<=sizeof(short)<=sizeof(int)<=sizeof(long)<=sizeof(long long)
Следует упоминуть конечно про MIPS-III где в unix системах было принято C ABI n32 в котором sizeof(void*)=4 что является весьма неплохой оптимизацией для 64bit систем. Однако архитектурно указатели все равно сериализовывались на стеке в 8 октетов со значением 0xff`ff`ff`ff в месте где положено быть старшей части (которое зависит от используемого endian)
Следует упоминуть конечно про MIPS-III где в unix системах было принято C ABI n32 в котором sizeof(void*)=4 что является весьма неплохой оптимизацией для 64bit систем.
На x86-64 есть полный аналог — x32.

Не знаю насколько он сейчас хорошо поддерживается, впрочем.

Потому что в мире существует очень много кода, который предполал, что sizeof(int) == 4, и вероятно всё ещё предполагает. Поэтому разработчики компиляторов решили сделать так, чтобы вызывать поменьше проблем на ровном месте при переходе на новую архитектуру. Где-то по тем же истерическим причинам слово word означает 16-битовое число, а double word — 32-битовое, несмотря на то, что у 64-битных процессоров слова 64-битовые.


В большинстве случаев, если вам не важен размер int, то 32-битного достаточно, а если важен, то вы используете intN_t.


Немного истории: http://www.unix.org/whitepapers/64bit.html

Жаль, что люди слишком поздно поняли прелесть фиксированых типов. Чаще всего ведь размерность идет из предметной области, а не из возможностей платформы.

С другой стороны, тип int будет оптимальным для платформы. Тот тип, с которым процессор работает без конвертаций и префиксов.
Если нужен счётчик на пару сотен итераций, использование для этого int16_t может привести к генерации медленного кода.

Можно, конечно, заморочиться с int_fast16_t, но такое мне не встречалось в реальных проектах.

Для счетчиков все таки годятся size_t и аналоги

Почему? Платформа x86-64 обладает особенностью: работа с нижними 32 битами регистров приводит к обнулению верхних 32 бит. Это даёт возможность полноценно использовать 32-битные счётчики без падения производительности в 64-битном режиме, без перерасхода памяти на 64-битные переменные и без возможных проблем, связанных с конвертацией между 32-битными и 64-битными значениями.


Собственно, привязка к size_t, да ещё и беззнаковому — это одна из причин, почему мне нравится стандартная библиотека в C++.

Хмм… Интересно.
А можно ссылку на ресурс, где сказано про обнуление? Безо всякого сарказма, действительно было бы интересно это прочесть в первоисточнике или хотя бы его реплике.
Документацию читать не пробовали?

Мне больше интересно какой-такой источник вы откопали, который об этом бы не говорил…
Тип size_t гарантирует возможность адресовать (использовать в качестве смещения) любой доступный объект в используемой модели памяти. Это его назначение. Как следствие этого он (и ssize_t) является результатом для разности однотипных указателей. Хотя для арифметики с указателями рекомендован ptrdiff_t из-за невнятного определения ssize_t в стандарте.
Тип size_t гарантирует возможность адресовать (использовать в качестве смещения) любой доступный объект в используемой модели памяти.
Это, мягко говоря, не совсем так. Возьмите Win16 (чтобы не привлекать экзотику). size_t там 16-битный, а указатели, внезапно, 32х-битные и памяти в системе может быть несколько сотен мегабайт.

Хотя для арифметики с указателями рекомендован ptrdiff_t из-за невнятного определения ssize_t в стандарте.
В стандарте C/C++ никакого ssize_t нету. Он есть только в POSIX.

Обратите внимание, кстати, что если массив столь велик, что разница между двумя указателями будет превышать PTRDIFF_MAX (но не будет превышать SIZE_MAX), то вы не имеете права такие указатели вычитать — это UB.
Возьмите Win16

Разве для Win16 существуют компиляторы, удовлетворяющие стандарту? Если нет, нельзя его "брать" в качестве релевантного примера.

Разве для Win16 существуют компиляторы, удовлетворяющие стандарту?
Строго говоря в мине нет ни одного существующего в мире компилятора, который бы на 100% соотвествовал стандарту. Ибо ошибки. Ну и ограничения, когда «не шмогла я, не шмогла».

На так-то да, разумеется для Win16 существуют соотвествующие стандарту C89 компиляторы — у них просто есть табличка в конце мануала, где описано где они C89 «ниасилили».

Если нет, нельзя его «брать» в качестве релевантного примера.
Вы сейчас издеватесь или где? Вы что — всерьёз хотите сказать, что раз сегодня, сейчас, у вас нету ни одно компилятора, соотвествующего C++20 стандарту, то нельзя в принципе обсуждать что и как такие компиляторы должны делать?

Что за бред. Важно не существование компиляторов, совместимых со стандартом, но возможность и разумность создания таковых.
Важно не существование компиляторов, совместимых со стандартом, но возможность и разумность создания таковых.

Cогласен. И с точки зрения возможности вы правы, возможно создать для Win16 компилятор с sizeof(size_t) != sizeof(void *). Но вот насчёт разумности я не уверен. Огромное число кода написано исходя из предположеня что указатели можно запихивать в size_t. И в отличие от написания кода с UB, написание на C++ кода непереносимого, но работающего, — древняя и святая традиция. Поэтому, возможно, всё же разумно считать, что всегда sizeof(size_t) == sizeof(void *) и делать гипотетический компилятор для Win16 так, чтобы это выполнялось.

А смысл это делать в ущерб эффективности?

На Win16, size_t — 16-битный регистр, void* — пара 16-битных регистров (сегмент+смещение).

Вы на ровном месте делаете ненужный оверхед.
Смотрю все же забыли про про модели памяти: tiny, small, medium, compact, large, huge и про far и near pointers.
Если нужен счётчик на пару сотен итераций, использование для этого int16_t может привести к генерации медленного кода.

… то следует использовать (u)int8_t, раз уж число итераций известно и влезает в 8 бит. Получится и логично, и оптимально. А если не влезает — необходимо взять тип большего размера — от этого никуда не уйти. Снова получится логично и оптимально (лучше всё равно нельзя). И не получится ситуации, когда 20-битное число перестаёт влезать в int, потому что он внезапно оказался 16-битным.


Я довольно-таки смутно представляю, как вообще можно писать работающий код, не зная размеров типов. Так запишешь число в переменную (регистр) — а оно не влезет. "Отлично оптимизировано", ага. Обычно обмазываю код compile-time assert'ами на такой случай.

Получится и логично, и оптимально.
На многих DSP будет нифига не оптимально. Ибо там обращение в память может быть только по словами и работать с 8-битовым (и даже 16-битовым) числом — это дикая конструкция с кучей сдвигов.

Обычно обмазываю код compile-time assert'ами на такой случай.
Все, кому интересен работающий код так делают.

Но многим это не нужно. Как это ни удивительно.
то следует использовать (u)int8_t, раз уж число итераций известно и влезает в 8 бит. Получится и логично, и оптимально
Нет, потому что если в цикле мы обращаемся по индексу в массиве, то у процессора есть команда типа
mov eax, [ebp+ebx*4]
но нет
mov eax, [ebp+bl*4]
Ему придётся делать
movzx edx, bl
mov eax, [ebp+edx*4]

и тратить ещё один регистр.

В части случаев компилятор сможет оптимизировать код, храня вашу uint8_t в 32-битном регистре. Но если вы передаёте этот 8-битный индекс параметром в функцию, которая не заинлайнилась, компилятору придётся сгенерировать инструкции расширения в полный размер слова. Это не только с индексацией массивов. Например, умножения тоже отсутствуют для переменных 8-16 бит, нужно расширять переменную до 32.
Например, умножения тоже отсутствуют для переменных 8-16 бит, нужно расширять переменную до 32.

Как это отсутствуют? Всё на месте:
https://www.felixcloutier.com/x86/add
https://www.felixcloutier.com/x86/imul


Просто никакого выигрыша они не дают, даже если нет необходимости вычислять адреса.

Например, умножения тоже отсутствуют для переменных 8-16 бит, нужно расширять переменную до 32.
Мелкая корректировка: они-таки существуют, но поскольку для 8-битных результат всегда попадает в аккумулятор компилятору не всегда удобно ими пользоваться. Но некоторые компиляторы умеют всё-таки.
Ему придётся делать
movzx edx, bl
mov eax, [ebp+edx*4]
и тратить ещё один регистр.

А разве нет инструкции 'movzx ebx, bl'? (я не силён в практическом ассемблере и не нашёл сходу подтверждений, что это не будет работать) Есть, конечно, инструкция 'and ebx, 0ffh', но это, наверно, немного не то по производительности.

Будет работать и то. И производительность одинаковая. И, что самое весёлое — ничего этого не нужно делать: если вы умножите 32битное число на 32битное, то в результате младший байт будет равен тому, что вам нужно.
тут вопрос не в умножении, а в чтении из массива по 8-битному индексу.
Как раз с size_t есть те же проблемы что и с любыми без знаковыми целыми, если их смешивать со знаковыми числами. Что сделать очень просто. Достаточно где-нибудь вычесть единичку.
Так что ssize_t,intptr_t,ptrdiff_t предпочтительнее. Но у C++ есть невменяемые оптимизации в режиме -O3 которые основываются на ложных умозаключениях, которые он выводит из факта переполнения знаковых чисел. И они вызывают другие не очевидные приколы.
Как раз с size_t есть те же проблемы что и с любыми без знаковыми целыми, если их смешивать со знаковыми числами.
Это совершенно другой вопрос. Речь шла про эффективность. На x86_64 вам любой не 64-битный тип, в любом случае, как бы там ни было, нужно будет расширять до 64-бит.

Да, иногда компилятор может воспользоваться тем, что записть в 32-битный регистр автоматически расширяет 32-битовое число без знака в 64-битовое число без знака.

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

Но у C++ есть невменяемые оптимизации в режиме -O3 которые основываются на ложных умозаключениях, которые он выводит из факта переполнения знаковых чисел.
Ну какие ещё «невменяемые отпимизации». «Следите за руками», не допускайте переполнения и будет вам «щастя».

В конце-концов вы всегда можете тупо завести переменную типа ptrdiff_t, получить в ней, в результате неверных рассчётов, -100 и тупо залезть в чужую память. И тут уже никакой компилятор вас не спасёт.

Да, C++ такой язык, он требует внимательности.
Достаточно где-нибудь вычесть единичку

что-то типа
for (size_t i = 0; i <= len-1; i++)
при len=0?
в данном случае вы можете переписать в виде
for (size_t i=0; i+1 <= len; i++)
и всё будет ок. Но обратите внимание что вы не можете пользовать правилами обычной математики ибо даже в неравенствах вы наступаете на то что вы регулярно вылазите за область определения. Т.к. работаете «на краю крыши без перил».
Но обратите внимание что вы не можете пользовать правилами обычной математики
Обратите внимание, что переход от unsigned к signed числам ничего не меняет ибо там область определения всё равно конечна (хотя там, как раз, компилятору разрешено считать, что она бесконечна).

даже в неравенствах вы наступаете на то что вы регулярно вылазите за область определения
То же самое будет с любыми другими не BigBum-типами. А если вы считаете, что BigNum для счётчика цикла — это «самое то», то нафига вам вообще всё это в C++ писать?

Есть много других языков, где об этом не нужно думать.

Т.к. работаете «на краю крыши без перил».
Если вы используете C++, то этот край оказывается ближе, чем вы думаете. Особенно если чиселки, с которыми вы работаете, поступают извне.
Обратите внимание, что переход от unsigned к signed числам ничего не меняет ибо там область определения всё равно конечна (хотя там, как раз, компилятору разрешено считать, что она бесконечна).
Одно дело когда область конечна и типовые значения далеко от этих границ. И совсем другое когда вы работаете рядом с такой границой.

Если вы используете C++
Да C++ интересный язык ideone.com/77ioHY в нём можно программировать с помощью UB
Одно дело когда область конечна и типовые значения далеко от этих границ. И совсем другое когда вы работаете рядом с такой границой.
Это да. В первом случае у вас куча дыр и отличное Job Security, во втором — вам-таки приходится думать над тем, что вы делаете.

Да C++ интересный язык ideone.com/77ioHY в нём можно программировать с помощью UB
Не очень понятно что вы имеете в виду. Что ваша программа работает и делает то, что вы хотели, но не то, что, как кажется, она должна бы делать с точки зрения «обычного читателя»? Ну так вам повезло просто, если программа вызывает UB (то есть не является программой на C++) то она вообще что угодно может делать.

Как бы всё чётко прописано:If any such execution contains an undefined operation, this document places no requirement on the implementation executing that program with that input (not even with regard to operations preceding the first undefined operation) (выделение моё).
Выдели-те то что позволяет выкинуть проверку условия цикла, на основании вывода в консоль. После чего цикл выполняется не 10 раз а 112.
Ну дык ваша программа не является валидной программой на C++ — какие у вас претензии к чему-либо?

Хорошо что 112 раз выполняется, а не миллион. и что винчестер не форматирует. Хотя могла бы.

«no requirement» == «no requirement», извините.
В каком месте она не валидная?
В том месте где умножает 3 на 1000000000. И да — программа может исполнять что угодно как после, так и до этого момента.

Вот не надо стандарт ставить во главу угла. Как-будто его писали трезвые люди.
Просто взглянем на код https://ideone.com/DyntQv


int main() {
    void show(int i,int x);
    int x = 27;
    for(int i=0; i < 10; ++i) {
        show(i,x);
        if(x==1) break;
        x = x%2 ? x*3+1 : x/2;
    }
}

Код в main не меняется. Он один и тот же. Но если функция void show(int i,int x) выше объявлена с inline или static то имеем 112 итераций цикла, а если нет то только 10.


Каким больным надо быть что бы писать такие стандарты? Почему просто не завершить аварийно компиляцию с матюками. Нет вместо этого надо молча сгенерировать максимально неадекватную реализацию.

Как-будто его писали трезвые люди.
Разумеется его писали трезвые люди.

Но если функция void show(int i,int x) выше объявлена с inline или static то имеем 112 итераций цикла, а если нет то только 10.
И в чём проблема, собственно?

Если ваш довод звучит как «неправильная программа должна либо всегда работать, либо не работать», то, извините, покажите мне любой язык с ручным управлением памяти, где следующая функция:
int foo(int *p) {
  delete[] p;
  int sum = 0;
  for (int i = 1000; i < 9000; ++i) {
    sleep(1);
    sum += p[i];
  }
  return sum;
}
...
  foo(new int[10000]());
...
всегда работает корректно и никогда не глючит. Особенно при налии в программе нескольких потоков.

Потом можно будет продолжить дискуссию.

На самом деле даже и языки типа Java или Python тоже могут подобные видеэффекты изображать, если разрешить загрузку сторонних модулей на C.

UB — это UB, если ваша программа его содержит, то и всё, собственно.

Каким больным надо быть что бы писать такие стандарты?
Наоборот — для этого нужно быть очень умными и вменяемым человеком. Как минимум знающим логику и имеющим здравый смысл.

Вы же сейчас поёте всё ту же песню что и прочие «нелюбители UB»: «почему мой автомобиль заставляет меня стоят в пробках при поездке на работу, а не телепортируется, чудесным образом, прямо из мой квартиры на работу». Ответ — потому что не может.

Нет вместо этого надо молча сгенерировать максимально неадекватную реализацию.
Почему «максимально неадекватную»? Сгенерированная программа работает вполне корректно для всех прогонов, в который не триггерится UB. И если вы скажите «но таких ведь не бывает»… то нет, это неправда.

Достаточно загрузить в вашу программу (можно через LD_PRELOAD) реализацию, при которой функция show отрабатывает пару раз, а на третий — кидает исключение и, внезапно, ваша программа станет корректной программой на C++ и, разумеется, будет корректно работать.

Почему просто не завершить аварийно компиляцию с матюками.
Нельзя. Потенциально UB возникает даже в такой чудесной функции как:
int int(int x) {
  return x + 1;
}
Согласитесь, что будет странно «аварийно завершать компиляцию с матюками» для подобного.

В рантайме проверки можно поставить задав опцию -fsanitize=undefined. Будет такое сообщение:
test.cc:6:45: runtime error: signed integer overflow: 3 * 1000000000 cannot be represented in type 'int'


Только ideone этого не позволяет.

P.S. На самом деле вам не нравится не то, как обрабатывается UB — его можно обрабывать, во многих случаях, только одним способом… вот именно так, как оно обрабатывается «если повезёт, то работает, не повезёт — извините». Ибо, скажем, при работе с «провисшими указателями» что-либо сказать можно только добавив в язык полноценный GC, который будет проверять в момент освобождения памяти — есть на неё ссылки или нет. Какая у этого высера будет скорость — даже представить страшно. Вам не нравится то, что переполнение целых чисел со знаком — это UB… Ну так вперёд — разработайте свой язык, «с блекджеком и шлюхами»… Так создатели Rust, к примеру сделали. Или -fwrapv используйте. Но капать слюной и обвинять разработчиков стандарта в том, что они идиоты — не стоит. Неконструктивно.
  1. Вы рассуждаете как религиозный фанатик. Такое поведение явное нарушение принципов программирования. Если хочется гарантий не переполнения диапазонов — введите тип данных который это проверяет и выкидывает исключение и т.п.
  2. В каком месте там выделение памяти?
  3. x+1 в модульной арифметике не вызывает вопросов. Почему, вернее зачем надо было в int искуственно присовывать такие правила — видимо наркотики не иначе.
  4. Если вы хотите стабильно работающую систему она должна стараться из любого недопустимого состояния переходить в рабочее, а не наоборот. Идти в разнос при первом удобном UB.
  5. Такая привязанность к UB может быть только у заядлых алкоголиков.
  6. Стандарт нужен только для того чтобы у всех работало одинаково. Иначе это не стандарт, а туалетная бумага.

ps: Скажу прямо: современный стандарт C++ гавно. Он не решает главных задач для каких был создан и движется не пойми куда.

Стандарт нужен только для того чтобы у всех работало одинаково. Иначе это не стандарт, а туалетная бумага.
Совершенно верно. Стандарт описывает — что именно и как должен делать программист для того, чтобы программы работали одинаково.

Это как контракт со службой доставки: мы доставляем ваши посылки по всему миру!.. при условии правильно указанного адреса.

А что будет если указать адрес с ошибкой? Вот C/C++ в этом случае разрешают программе отправить посылку куда угодно.

x+1 в модульной арифметике не вызывает вопросов.
Вызывает. Если у вас используется дополнительный код — будет одно значение, если обратный — другое.

А обратный код до сих пор кое-где поддерживается (обратите внимание на год выхода последней версии).

Потому писать программы, приводящие к переполненияю программистам запрещено. Иначе переносимости не будет.

В каком месте там выделение памяти?
Выделение памяти я использовал как пример другого UB, по поводу которого, обычно, вопросов не возникает. Но вы поступили ещё круче. Изобразили настоящую «гуманитарную одарённость» в известном стиле:


Если вы хотите стабильно работающую систему она должна стараться из любого недопустимого состояния переходить в рабочее, а не наоборот.
Это обязанность программиста. Для помощи ему есть статистические анализаторы кода и всякие дополнительные тулзы.

Идти в разнос при первом удобном UB.
Не делайте UB, ничего не будет «идти в разнос».

Такая привязанность к UB может быть только у заядлых алкоголиков.
Такая привязанность к UB имеется во всех компиляторах всех имеющихся в природе языков программирования.

Просто некоторые языки (как-то: Haskell, Java, Rust) имеют подмножество, не позволяющее вызвать UB, а часть языков (как-то: Assembler, C/C++, Pascal) — его не имеют.

Если хочется гарантий не переполнения диапазонов
Хочется не «гарантий непереполнения диапазонов». Хочется переносимых программ, которые везде и всюду работают одинаково.

введите тип данных который это проверяет и выкидывает исключение и т.п.

Это будет другой подход и с совсем другими принципами. В языках C/C++ принят подход при котором часть работы по поддержанию этих гарантий — возложена на программиста, только и всего.

Такое поведение явное нарушение принципов программирования.
Это, извините, что за принципы и какой-такий всемирный орган сделал их обязательными?

В разных языках — разные принципы, извините. В Java принцип «UB может быть порождён только нативным кодом, вызванным через JNI — следить за ним обязанность программиста, в остальном коде за этим следит компилятор», в Rust принцип «UB может быть порождён только unsafe кодом — следить за ним обязанность программиста, в остальном коде за этим следит компилятор».

А в C/C++ прницип самый простой: «UB может быть в программе в тех местах, где это описано в документации, они никак не выделены — следить за ним обязанность программиста»… и всё.

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

Ещё раз повторяю мой тезис. Ваши претензии к UB — сводятся не к тому, что компилятор «неправильно обрабатывает» UB. В конце-концов вы не желуетесь, что аллокация строки на 10 байт и запись туда 20 байт может привести к катасрофе и не предлагаете, из-за этого, отменить new и delete. Ваши претензии сводятся, по большому счёту, к пункту #3: некоторое действие, которое вы хотели бы считать определённым компилятор трактует как UB — и действует соответствующе.

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

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

Да, по большому счёту этого и не нужно даже. И у gcc и у clang уже есть соотвествующая опция!

Почему бы вам ей не воспользоваться, если вы так уж хотите писать программы с этим, конкретным, UB?

ps: Скажу прямо: современный стандарт C++ гавно. Он не решает главных задач для каких был создан и движется не пойми куда.
Ну — это совсем другой вопрос. И, кстати, уже ваша формулировка показывает ваше неумение думать. Потому что C++, несомненно, решает те задачи, для которых он был создан (написание переносимого низкоуровневого кода операционной системы), но вот только неочевидно, что в современных условиях именно это — «главные задачи», для которых он используется…

Но этот вопрос не имеет никакого отношения к тому, как реагировать на UB. Реакция на UB у всех языков программирования — одинакова. Отличается только ответ на вопрос «а что мы считаем UB, что не считаем». И тут да — список UB в C++, возможно, несколько не оптимален…
Стандарт описывает — что именно и как должен делать программист для того, чтобы программы работали одинаково. Это как контракт со службой доставки: мы доставляем ваши посылки по всему миру! при условии правильно указанного адреса.
А что будет если указать адрес с ошибкой

Нормальной реакцией является возврат отправителю. А не действие на усмотрение почтальона.

>> x+1 в модульной арифметике не вызывает вопросов.
Вызывает. Если у вас используется дополнительный код — будет одно значение, если обратный — другое.

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

А обратный код до сих пор кое-где поддерживается

Я разве против. Пусть продолжает кое-где поддерживаться.

Потому писать программы, приводящие к переполненияю программистам запрещено. Иначе переносимости не будет.

Что за глупость? Переполнении чего? Регистров процессора или типа данных?
Если вам нужны типы данных с контролем переполнения так введите их явно.
Для чего раскладывать грабли там где они совершенно не нужны.

Выделение памяти я использовал как пример другого UB, по поводу которого,
обычно, вопросов не возникает.

А Вы скользкий тип. Я спросил вполне конкретный вопрос. Почему нарушаются абстракция черного ящика?
Почему программист должен вникать в детали и особенности реализации внешних сущностей.
Которые вдруг радикально меняют поведение программы.

>> Если вы хотите стабильно работающую систему она должна стараться из любого
>> недопустимого состояния переходить в рабочее, а не наоборот.
Это обязанность программиста. Для помощи ему есть статистические анализаторы кода и всякие дополнительные тулзы.

Именно по тому что есть люди подобные вам приходится изобретать такие инструменты.

Не делайте UB, ничего не будет «идти в разнос».

Чем новее стандарт тем за большим количеством блох приходится следить.
Программирование не должно превращаться в игру в наперстки.

Такая привязанность к UB имеется во всех компиляторах всех имеющихся в природе языков программирования.
Просто некоторые языки (как-то: Haskell, Java, Rust) имеют подмножество, не позволяющее вызвать UB, а часть языков (как-то: Assembler, C/C++, Pascal) — его не имеют.

Продемонстрируйте UB на примере Assembler-а

Хочется не «гарантий непереполнения диапазонов». Хочется переносимых программ, которые везде и всюду работают одинаково.

Поэтому надо сделать всё для усложнение этой работы.

Это будет другой подход и с совсем другими принципами. В языках C/C++ принят подход при котором часть работы по поддержанию этих гарантий — возложена на программиста, только и всего.

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

Это, извините, что за принципы

en.wikipedia.org/wiki/KISS_principle
и какой-такий всемирный орган сделал их обязательными?

Человеческий мозг.

А в C/C++ прницип самый простой: «UB может быть в программе в тех местах, где это описано в документации, они никак не выделены — следить за ним обязанность программиста»… и всё.

Именно поэтому такой стандарт гавно. Где всё больше и больше бессмысленной работы сваливают на программиста.

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

Я рад за вас! У вас очень здоровое соответствующее самомнение.

Ваши претензии сводятся, по большому счёту, к пункту #3: некоторое действие, которое вы хотели бы считать определённым компилятор трактует как UB — и действует соответствующе.

Нет, меня не устраивает то что он считает соответствующим.

Ну так это вопрос… заботы о поддержании совместимости со всякими UNIVAC-ами.

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

Ну так это вопрос внесения соответствующих изменений в стандарт — и не более того.

Да так просто. И где эти соответствующие изменения?

Да, по большому счёту этого и не нужно даже. И у gcc и у clang уже есть соотвествующая опция!

Да и не говорите. Лучше бы соответствующую опцию сделали для соответствующих UNIVAC-ов

Почему бы вам ей не воспользоваться, если вы так уж хотите писать программы с этим, конкретным, UB?

Выше вы писали что умеете рассуждать. Я не хочу писать программы с UB. Это просто пример.
На практике ситуации гараздо сложнее и выявить подобные ситуации не просто.
Именно по этому приходится изобретать всякие тесты, статистические анализаторы кода и дополнительные инструменты. Потому что соответствующие умельцы рассуждать считают, что и так сойдёт. И всё хорошо и соответствующее соответствует соответствующим пунктам в соответствующем стандарте.
Если совсем выпирает то добавим в список еще один UB. И назовём этот баг фичей.

Потому что C++, несомненно, решает те задачи, для которых он был создан (написание переносимого низкоуровневого кода операционной системы),

Именно поэтому используют C и пытаются переписать на Rust и другие языки.
Было бы всё так замечательно не было бы этих попыток.

но вот только неочевидно, что в современных условиях именно это — «главные задачи», для которых он используется…

В каких таких условиях? Внезапно появилось огромное количество вычислительных ядер. Спекулятивное исполнение, выравнивание и векторизация. Уже есть соответствующе UB и для них.

Но этот вопрос не имеет никакого отношения к тому, как реагировать на UB.
Реакция на UB у всех языков программирования — одинакова.

Очень интересно? И какя же?
Самая правильная реакция на отказ в Erlan: Не смог — умри, не создавай UB остальным.

Отличается только ответ на вопрос «а что мы считаем UB, что не считаем».

Вопрос не в этом, а в том что C++ из обычного инструмента, превращают в чёрте что.

И тут да — список UB в C++, возможно, несколько не оптимален…

А вы шалун, умеете вы слова подбирать.
Продемонстрируйте UB на примере Assembler-а

Ну, например, вот:


MOV EAX, 42
MOV CL, 33
SHL EAX, CL

И да, мне тоже не нравится UB в том виде, который описан в стандарте C++. Просто C++ позиционируется как универсальный язык под любые платформы. В нём даже количество бит в байте не фиксировано, насколько я знаю.

Это насколько помню UB процессора, вообще ужас ужас. )
Думаю на ассемблере будет что-то типа:

RDRAND RAX
JMP RAX


или просто

MAIN:
JMP RSP
Ну вообще то, что вы написали даст вам вполне предсказуемый результат на x86_64: 84.

А вот подобная же программа на ARM, внезапно, даст в ответе 0. Хотя вроде на Arch64 тоже 84.

Но чтобы с этим не возиться — язык C (и за ним C++) просто говорят: сдвигать на 33 низзя, кто так сделает — ССЗБ.

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

А дальше… UB «распросраняется по программе»: от той точки где UB, реально случилось, до той точки где программа начнёт вести себя… «странно» — может быть очень большое расстояние.
shl eax,cl не влияет на EDX и другие регистры в случае CL=33
Вот если бы он влиял то да было бы не предсказуемое поведения. А так он просто использует младшие 5бит регистра CL где тут UB?
Вот это UB
shl eax,cl не влияет на EDX и другие регистры в случае CL=33

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


Вот это UB

Это не UB, это ошибка в процессоре.

Там многоходовка.
  1. Некоторые действия вызывют непредсказуемое поведение, даже если у вас очень просто и тупой компилятор — асинхронное обращение к переменным (на Archive.org есть куча книг про то, как с этим бороться — в C64, AmigaOS, MS-DOS, MacOS и так далее… хотя C64 и MS-DOS, вообще-то, на ассемблере написаны, где «UB не бывает»), работа с освобождённой память (хотя это, в общем-то разновидность того же самого), работа с 8087 (и опять — та же песня). Настоящее UB, которое невозможно изгнять из компьютера никак — возникает когда компьютер начинает общаться с реальным, физическим, миром!
  2. Некоторые действия, которые, в голове программиста (особенно современного программиста, знающего «две с половиной архитектуры») «никогда не приводят к непредсказуемой работе процессора» — на самом деле, на некоторых архитектурах, часто даже не слишком экзотичнских — могут это делать… или могли это делать в прошлом — вот классически, через тайминги и «общение с внешним миром».
  3. C/C++ стандарты, для того, чтобы программы были переносимыми все эти конструкции — запретили и занесли в категорию UB.
  4. Компиляторы, для своих оптимизаций — опираются на описание работы программ в стандарте. Ибо без этого, фактически, никакие оптимизации невозможны, так как для любой из них (даже для банального чтения переменной из памяти один раз вместо двух) можно написать программу, которая это «заметит»… такая программа, конечно, неизбежно будет содержать в себе UB… возможно undefined behavior, но, как минимум, unspecified behavior (ситуация, когда возможен один из нескольких допустимых результатов, но не может произойти «что угодно»). Заметьте, что это так даже в безопасных языках типа Java.
  5. И вот тут-то мы наступаем на очень тонкую развилку. В языке Java нет undefined behavior (хотя полно unspecified). И некоторые программисты, почему-то, считают, что C/C++ должен быть устроен так же. И что многие вещи, которые сейчас являются undefined behavior, должны быть, на самом деле unspecified behavior… звучит разумно.
  6. Но пока стандарт говорит, что это — undefined behavior не очень понятно почему компиляторы должны генерировать неоптимальный код.


Ну существует архитектур, для которых обычные арифметические операции приводили бы к непредсказуемой работе процессора или даже к порче аргумента.
Существует. Банальный MIPS. Переполнение при сложнее вызывает обработчик прерывания — и уж что там это обработчик натворит в вашей программе… одному богу ведомо. Вся классика влияния таймингов на вашу программу и доступа к переменным без синхронизации — в вашем распоряжении. Вплоть до того, что всё может работать без проблем пока у вас переполнения случаются редко, но привести к беде, когда они случатся одновременно на двух CPU.

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

Но стандарт C++ все равно позволяет компиляторам в этом случае творить дичь.
Потому что так проще. Не забыли? Первый принцип. Простота реализации важнее простоты интерфейса.

И да, с тем, что в стандарте C/C++ уж как-то слишком много undefined behavior и мало unspecified и implementation defined… я согласен. Но оно само по себе не исправится! И гневные статьи на Хабре — ничего не изменят.

Хотите изменений в стандарте — готовьте предложения в комитет по стандартизации и готовьтесь его обсуждать.

По другому — это не изменится, извините. Все самые «страшные UB», на которые больше всего жалоб — идут из глубины веков.

Возьмите, к примеру, совсем «свеженький», ему Clang в 10 версии научился (пока warning, оптимизации на основе этого будут потом): операции с nullptr — это UB. Почему так? А потому что гладиолус.

На всё том же самом Win16 у вас указатель — 32-битный, но какая-то арифметика имеет смысл только на младших 16 битах (есть специальный huge указатели и специальные функции… но это отдельно, для обычных указателей это так). Соотвественно прибавляя любое, даже 32-битное число к nullptr получить вам валидный указатель… не судьба. Ну и, соотвественно, стандарт честно пишет, вот даже прямо в самой первой версии пишет, что это UB… но людям-то пофигу! Они лучше знают как устроен мир, чем коммитет по стандартизации. </sarcasm>.

Вот отсюда вся эта буча и растёт…
Это именно поведение которое не было определено заранее (undefined behavior).
Но стандарт C++ все равно позволяет компиляторам в этом случае творить дичь.

Да об этом я и говорю.
UB довольно типичная ситуация на неопределённых для процессора опкодах.
Это ещё со времён Z80, процессоры, сделанные самим Zilog и его клоны работали по-разному на опкодах, не определённых в даташите.

И сейчас тоже самое: запрещённые опкоды кидают исключение UD, но никто не гарантирует, что в следующем поколении процессоров эти опкоды не будут использованы под какой-нибудь AVX-1024. То есть, эти опкоды сейчас для произвольного x64 — чистый UB.
Продемонстрируйте UB на примере Assembler-а
Да легко. Возьмите любую статью или книгу, описывающую как писать TSR (скажем эту) и можете начинать просвещаться на тему UB, происходящих при «неаккуратном» вызове DOS из TSR-программы, INDOS флаг и всё такое прочее.

И DOS и все программы (по крайней мере ранние) под него — это чистый ассемблер, тем не менее, ситуация «запустил 2-3 программы и вся система стала жестоко глючить» — скорее правило, чем исключение.

Ну или HCF банальный (когда единственный способ вывести процессор из состояния «задумчивости» — это его полностью обесточить).

А Вы скользкий тип. Я спросил вполне конкретный вопрос. Почему нарушаются абстракция черного ящика?
Потому что в любой системе, всегда есть вещи, от которых защититься невозможно.

Есть только два варианта:
1. Либо мы запрещаем некоторые полезные и осмысленные действия — во имя безопасности.
2. Либо мы позволяем некоторые опасные и бессмысленные действия — во имя эффективности.

Теорема Райса и всё такое прочее.

Почему программист должен вникать в детали и особенности реализации внешних сущностей.
Каких ещё «внешних сущностей»?

Которые вдруг радикально меняют поведение программы.
Если программа является корректной программой на C или C++ — то не меняют.

Поэтому надо сделать всё для усложнение этой работы.
В каком месте вы обнаружили усложнение?

Это, извините, что за принципы
en.wikipedia.org/wiki/KISS_principle
А. Это-то. Ну так C/C++ им и следуют. Для того, чтобы упростить написание компилятора — обязанность избегать определённых ситуаций возложена на программиста.

Программирование не должно превращаться в игру в наперстки.
Извините, но этот поезд давно уехал. Unix, C и C++ с самого начала следовали KISS: реализация и интерфейс должны быть простыми. Простота реализации даже несколько важнее простоты интерфейса. Простота — самое важное требование при выборе дизайна

Всё что вы тут наблюдаете — это следствия примения этого подхода. Почитайте в оригинальной статье обсуждение «PC loser-ing problem».

Где всё больше и больше бессмысленной работы сваливают на программиста.
С этого момента — поподробнее, пожалуйста. А то вы тут уже всех завалили бессмысленными, неверными обвинениями.

Чем новее стандарт тем за большим количеством блох приходится следить.
Пока что вы тут продемонстрировали одну блоху: неопределённое поведение при переполнении. Можете открыть стандарт 1989го года и убедиться что прямо в разделе 3ем (там где определения, ещё до текста, собственно, самого стандарта), в качестве эталонного примера «неопределённого поведения», которого программист должен избегать называется «behavior on integer overflow».

О каком более раннем стандарте, где этого не было, вы говорите — я не в курсе. Может просветите публику?

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

Нафига козе баян. Сделайте модификацию для всяких UNIVAC-ов. Зачем остальным жизнь портить.
Очень просто: в 1989м году, когда писали первый ANSI стандарт C — это было актуально. Потому он и написан так, как написан.

Претензии не к UB, а к тому что их становиться чем дальше тем больше.
Нет. Не становится. Все ваши пышь-пыщь-праведный-гнев, я такой д’Артаньян — они, внезапно, касаются вещей описанных ещё в том самом первом ANSI C стандарте 1989го года.

Да так просто. И где эти соответствующие изменения?
Пример — я уже приводил, извините. Можете не полениться и всё-таки его прочитать.

И нет — просто потому что вам так захотелось никто ничего менять не будет, нужно достаточно веское обоснование. Тот факт что «ну мне так очень хочется» — обоснованием, извините, не является.

Лучше бы соответствующую опцию сделали для соответствующих UNIVAC-ов
Ну было бы странно если бы по умолчанию компилятор C++ был бы рассчитан не на программы на C++, а на программы на каком-то странном над/под/множестве, согласитесь?

Я не хочу писать программы с UB.
В таком случае, извините, вам нужен другой язык. Не C и не C++. Потому что они построены вокруг UB. Весь их дизайн заключается в выписывании вещей, за которомы должен следить программист — и обработке некоторых из них компилятором.

А не наоборот, как в Java, к примеру (где при написани обычного кода обычным способом вы вызвать UB не можете, а должны лезть для этого в недра системы и вызывать разные функции из системных пакетов).

На практике ситуации гараздо сложнее и выявить подобные ситуации не просто.
И именно поэтому эта задача возложена на программиста. Чтобы упростить компилятор.

Именно поэтому используют C и пытаются переписать на Rust и другие языки.
Не видел ни одного ядра на Rust пока что. Одни разговоры. Извините. А вот на C и C++ — видел и не одно.

То, что C (и C++) решают задачу, для которой их создали — не обозначает, что они идеальны. Всегда можно хотеть лучшего. Но Rust, пока что, не доказал что он может лучше.

Уже есть соответствующе UB и для них.
Всегда были. До C11/C++11 вообще любая программа на двухядерной системе содержит UB. Просто потому что более ранние версии вообще разрешали компилятору предполагать, что любое изменение любой переменной (не помеченной как volatile) средой (в частности вторым потоком) — это UB.

Очень интересно? И какя же?
Любая. Может быть всё что угодно. Почитайте книжки про TSR в DOS. Там бывает масса самого разнообразного и интересного.

Вопрос не в этом, а в том что C++ из обычного инструмента, превращают в чёрте что.
Он никогда не был «обычным инструментом». Вам могло просто так казаться, если вы использовали строго один-единственный компилятор под один-единственный процессор и запускали программу на одной единственной OS. Шаг вправо, шаг влево — катастрофа.

Если вы считаете что сегодня, когда вам нужно, всего-навсего, открыть стандарт и прочитать, что там написано всё ужасно — то вы, что называется, «с жиру беситесь».

В 1990е такой роскоши вы себе позволить не могли, приходилось тащить кучи костылей для самых разных компиляторов.
Лучше будет так:
for (size_t i=0; i < (size_t)len; i++)
Тут i<len достаточно (но надо заранее знать, что это может вылезти боком).

Можно столкнуться при ходе назад, как в этой дискуссии:

for (i = N - 1; i >= 0; --i) {
    ...
}


совершенно типовая ошибка того, кто не обжёгся ещё.
Многие компиляторы предупреждают «condition is always true», но это надо 1) читать предупреждения, 2) понимать, о чём они :)

В результате и выработалась идиома типа

for (i = N; i--; ) {
    ....
}

где несмотря что на выходе i == (typeof i)-1 == ~0, формально всё поведение законно.

Ещё тут получилось неплохое обсуждение темы, с акцентом на Rust и циклы с постпроверкой.
Не пойдёть, извините. Для int — у вас та же самая фигня
Да, согласен. Тут int32 имеет накладные расходы.
и тратить ещё один регистр.
А разве нет инструкции 'movzx ebx, bl'?
Почему у меня возникло такое решение? Формально, это действие трогает регистр, в котором лежит переменная. Так что решение в лоб — создать копию и преобразование для адресации делать на копии. Конечно, компилятор может знать, что
and ebx, 0xff
фактически не меняет переменную unsigned char в регистре bl, но на это у компилятора должны быть дополнительные правила.

Увы, int не всегда самый оптимальный. Int не будет больше 4 байт. Так что если для процессора самый оптимальный тип 8 байт, то int не сможет быть оптимальным типом.

Можете привести пример процессора, для которого оптимальный тип — 8 байт, а компилятор генерит 4-байтовые инты?


Сразу отмечу, что для x86-64 и ARM64 оптимальный размер — 4 байта.

А можете привести архитектуру, для которого sizeof(int) больше четырёх?


Кста, gcc для int_fast32_t и int_fast16_t использует 8 байт на amd64.

Тем не менее,
for (int i = 0; i < 1000; i++) ...
такой же оптимальный на x64, как и
for (int64_t i = 0; i < 1000; i++) ...

Речь немного про другое шла. Если у нас есть гипотетическая архитектура, для которой оптимальным типом является 8 байт, при этом эта архитектура поддерживает 2 и 4 байта, то int никак не может быть 8 байт. Значит int не всегда может быть самым оптимальным типом.

int никак не может быть 8 байт
В чём проблема? Может быть 64-bit int

Допустим, у нас есть платформа, для которой оптимальным целочисленным типом является 8 байт. Эта платформа также поддерживает типы: 1, 2, 4 байта. В этом случае char будет 1 байт. Попробуем сделать int 8 байт. Тогда у нас остаётся один тип в Си (short) и два типа платформы (2 и 4 байта).

Допустим, у нас есть платформа, одинаково хорошо поддерживающая 1,2,4,8,16,32,64 байта. Всё, по вашей логике C на ней не будет, потому что закончились ключевые слова?

Никто не мешает int сделать 64-битным, а для 2-4 байтных типов использовать int16_t, int32_t, которые будут маппится на известные компилятору внутренние типы __int16, __int32
Никто не мешает int сделать 64-битным, а для 2-4 байтных типов использовать uint16_t, uint32_t, которые будут маппится на известные компилятору внутренние типы __int16, __int32
Если ваша цель — выиграть приз в каком-нибудь конкурсе компиляторов… то не проблема.

А если ваша задача — поддерживать как-то существующий код… то мешает. Тот факт, что на существующих платформах uint16_t и uint32_t — это short и int, соответственно. Вы не можете сделать overloading для short, int, uint16_t и uint32_t, потому что компилятор начнёт вопить, что вы одну и ту же функцию переопределяете.

Да, можно, через игру с SFINAE попробовать что-то такое соорудить… и поддерживать как платформы где uint16_t и uint32_t мапируются на известные типы, так как на такую экзотику, про которую вы говорите, но… практически — программисты не такие маньяки, обычно делаются реализации на char/short/int/long/long long и дальше надеются на то, что int8_t/int16_t/int32_t/int64_t как-то туда да «вложатся»…
Если у нас новая архитектура, в которой 32-битный int неэффективен, а 64-битный эффективен, то переход будет больше поход на переход с 16-битных систем (80286) на 32-битные. То, что отвалятся перегрузки — не такая большая проблема в глобальном плане.
то переход будет больше поход на переход с 16-битных систем (80286) на 32-битные
Unix его, фактически, не заметил (Unix System III работает как на 16-битной PDP-11, так и на 32-битной VAX, разделяя большую часть исходников), в Windows — тоже переход с Win16 на Win32 прошёл достаточно гладко (не сложнее, чем переход с Win32 на Win64).

Вы сейчас вообще о чём?
Не знаю, как с Unix, но с этим я не согласен:
в Windows — тоже переход с Win16 на Win32 прошёл достаточно гладко

В Windows довольно долго существовали 16-битные NE-executables. Можно считать, две платформы существовали совместно. А чтобы сделать 32-битное приложение, нужно было существенно переписывать.
А чтобы сделать 32-битное приложение, нужно было существенно переписывать.
Не сказал бы, что «существенно». Несколько макросов по разному работали. И всё, фактически.

Вот наоборот — там сложнее. В Win16 куда больше ограничений, которых в Win32 просто нет.
Чаще всего ведь размерность идет из предметной области, а не из возможностей платформы.
Проблема в том, что в случае с «ранним C» это одно и то же. Он же затачивался строго под то, чтобы переносить «переносимую операционку» — а в этом случае «предметная область» и «возможности платформы» это одно и то же.

Жаль, что люди слишком поздно поняли прелесть фиксированых типов.
Ещё даже в 1989м было полно платформ, устроенных совсем иначе, чем «плоская память» и «дополнение до двух».

Обратите внимание: даже в самых последних стандартах типы фиксированных размеров (int32_t или intptr_t) — опциональны.

Потому что до int есть только два типа: char и short. Char всегда 1 байт, если мы сделаем int 8 байтами, то не будет типа, который означает либо 4, либо 2 байта.

Но байт — это не обязательно 8 бит. Если будет 18-битный байт и 18-битный int, то всё норм. Другое дело что эпоха такой экзотики давно ушла.
Вы на заголовок статьи смотрели? Какой 18-битный байт на x86-64 ???
Смотрел. Но речь и в обсуждении ушла далеко от заголовка.

Вопрос был почему int не может быть 8 байт. А при чём тут биты, вообще, не понятно. Sizeof в байтах измеряет, а не в битах, же.

Байт — производное понятие. Сейчас наверное практически нету архитектур с размером байт отличным от 8 бит, но это не значит, что такого в принципе не может быть.
Это всё прекрасно, но, ещё раз: какое это всё имеет отношение к x86-64?

Или вы не в курсе о том, что Microsoft придумал для x86-64 название x64?

Ну так я и не отрицал, что бывают архитектуры, где в байте больше 8 бит. Только если на этой архитектуре есть типы 1, 2, 4 байта, то int не может быть БОЛЬШЕ 4 байт, ибо тогда не будет типа, который описывает либо 2, либо 4 байта. До int у нас есть всего 2 типа: char и short. Char всегда равен одному байту. Если, условно, мы делаем int 8 байтам, то у нас остаётся только short, а нам нужно описать два типа: 2 байта и 4 байта.

Совершенно не обязательно. Они все могут быть по 8 байт. А чтобы использовать 2- и 4-байтовые типы, существуют fixed width integer types. Как они реализованы под капотом, уже не имеет значения.


char — вообще недоразумение. Хотя бы потому, что это не интегральный тип, а символьный.

Согласно стандарту C — char "интегральный" (целочисленный) тип. Fixed width integer types — просто ссылки на "фундаментальные" типы. Фундаментальных целочисленных типов всего 5: char, short, int, long, long long. Если, ни short, ни int, ни long, ни long long не 4 байта, то не будет fixed width integer type на 4 байта.

нет требования, чтобы Fixed width integer type был ссылкой на фундаментальный стандартный тип, а не на внутренний тип компилятора.
Дык там и требований, чтобы эти типы, в принципе, присутствовали — нету. И даже intptr_t, формально, опционален.

Разработчики Ed2k попробовали, в какой-то момент, сделать компилятор, который этого всего не предоставляет — догадайтесь как много программ с этим компилятором собралось и сколько потом заработало.
Так, минуточку, я не предлагаю не определять int16_t. Он будет определён, но не будет совпадать с каким-то из типов short/int/long.

Какой-то компромис придётся принять. Либо смотрим в будущее, и делаем удобным написание нового кода, с быстрым int-ом, либо делаем эмуляцию старой архитектуры. Да, оно заведётся, и портировать старый код будет быстрее. Но что если с коротким int-ом оно будет работать в 2-3 раза медленнее? Всё равно придётся ВСЁ переписывать на правильные типы, чтобы полностью раскрыть производительность платформы. А потом за это кривое решение вас будут каждый раз поминать недобрым словом, а вы им — «так сложилось, надо же было legacy по-быстрому запустить».
Да, оно заведётся, и портировать старый код будет быстрее. Но что если с коротким int-ом оно будет работать в 2-3 раза медленнее?
Разработчикам процессора сделают большой втык и они проблему решат?

Не первый раз же! Когда Pentium Pro плохо работал с Windows 95 — правили вовсе не Windows 95, извините.

Всё равно придётся ВСЁ переписывать на правильные типы, чтобы полностью раскрыть производительность платформы.
Если при переходе на какую-то платформу нужно переписывать ВСЁ — то это отличный экспонат для музея компьютерной истории. И нужно поторапливаться, потому что кроме опытной партии могут ведь ничего и не выпустить!

А шансов у такой платформы стать популярной — в точности нуль.
Вы говорите о массовых универсальных процессорах.
Но Си-компилятор может потребоваться дла какого-то суперкомпьютера с экспериментальной архитектурой, или для специализированного чипа, на котором будет крутиться ровно одна программа.
Для чего он там «может потребоваться»? Если для компиляции и запуска существующих программ — то вам всё равно придётся мириться с теми, что они написаны… так как написаны.

А если вы всё равно собрались всё с нуля писать — то зачем вам для этого C? Тем более «для специализированного чипа, на котором будет крутиться ровно одна программа»?
Что, если не C?
Понятия не имею. Объясните — зачем вам свой процессор потребовалось разрабывать, потом можно будет что-то обсужать.

Может вам и вообще языки программирования высокого уровня не понадобится.
Мимо темы, но некоторые таким «развлекаются» в рамках курсовых :)
Какая-нибудь экспериментальная архитектура, например, удобная для векторных вычислений, но и со скалярными нативными 64-128 битными значениями.
Зачем свой? Например, не платить за лицензирование.
Ну значит заплатите за переписывание кучи кода.

Было бы интересно поговорить с кем-нибудь из МЦСТ.

Они там тоже, наивные чукотские вьюноши, ожидали, что смогут просто реализовать стандарт C (без intptr_t и прочих «излишеств», которые плохо ложатся на защищённую архитектуру E2k) и получить кучу софта.

Ага. Щаз. Разбежались. 90% кода действительно компилируется без проблем. К сожалению оставшиеся 10% — это нифига не что-то «на переферии», а, наоборот, основа, над которой эти 90% надстроены. Самые глубинные, базовые библиотеки.

То есть получается фигня: скопилировать-то вы можете много чего, тест на компилятор отличный… а запускать нечего.

К сожалению у меня там контактов нету, все люди, с которыми я говорил — это люди, которые больше в МЦСТ не работают.

Интересно было бы узнать чем вся история кончилась.
да нет вообще-то, char может быть и 32 бита.
Есть только гарантия sizeof(char) = 1 (не байт, а условная единица, char). На такой платформе скорее всего будет sizeof(char) = sizeof(short) = sizeof(int) = 1; sizeof(long) = 2
А причём тут это?

Речь-то идёт о конкретно x86-64. Там есть поддержка всех «естественных» типов: 8-бит, 16-бит, 32-бит, 64-бит…

А имён в стандартной библиотеке только четыре (char/short/int/long) с половиной (есть ещё long long, но он только с C99/C++11). Вот их и распределили…

По стандарту C char имеет такое же количество бит, что и байт.

Тут можно спорить до бесконечности. Когда-то давно байт мог быть не 8-битным просто.

И стандарт C унаследовал именно такое понимание.

Чтобы не было путаницы лучше говорить что sizeof(char) == 1, но при этом char не обязан быть октетом.

В ранних сетевых RFC этот термин популярен…
Загляните в стандарт, типов гораздо больше и они не обязаны быть определены с помощью базовых языковых типов. По поводу линейки char, short, int, long есть только одно ограничение которое состоит в том что размер следующего в линейке не меньше предыдущего, но существуют еще типы с фиксированным размером контейнера и представлением (например int в ILP32 не обязан выражать 32bit отрицательные числа через доролнительный код а int32_t обязан), с минимально гарантированным или оптимальным размером контейнера.
Загляните в стандарт, типов гораздо больше и они не обязаны быть определены с помощью базовых языковых типов.
Если бы типов было бы больше — беды бы не было.

К сожалению int8_t, int16_t и так далее не являются отдельными типами, они обычно являются синонимами других типов и хотя стандарт ничего на говорит на тему «а точно ли нет никаких типов, кроме char/short/int/long/long long»… но куча кода предполагает, что ответ на этот вопрос «да». Это и всевозможные процедуры форматирования и метапрограммирование и куча всего ещё.
Не, не везде. В паре мест по тексту ещё. И в тегах.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий