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

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

Не раскрыт вопрос про size_t, ptrdiff_t, intptr_t, uintptr_t.
Я и про классические int, short… не всё рассказал. Так и задумано. Целью поста было рассмотрение только скользких вопросов. Типы size_t, ptrdiff_t, intptr_t, uintptr_t самоописательны и довольно предсказуемы.

Подробное и полное описание всех целочисленных типов есть в стандарте. Читайте его.
Напомнило дурацкий вопрос на собеседованиях и тестах, что возвращает sizeof():
  1. int
  2. unsigned int
  3. size_t
  4. ...
size_t — беззнаковый целочисленный, в который гарантированно может поместиться размер теоретически самого большого массива данных в системе, в частном случае является результатом sizeof();
ptrdiff_t — знаковый целочисленный, в который гарантированно может поместиться размер теоретически самого большого массива данных в системе, является результатом операции вычитания одного указателя из другого;
intptr_t, uintptr_t — знаковый и беззнаковый целочисленные типы, способные безопасно хранить указатель независимо от платформы.
А правда, что sizeof(void*) == sizeof(long)?
В сорцах Linux ядра видел косвенное тому подтверждение.
Вообще говоря, нет.
void* — это все-таки указатель, и его размер в 32-разрядной системе составляет 4 байта, как написал автор статьи, long — это как минимум 4 байта. Поэтому правда, но для x86 и аналогичных платформ.
По стандарту нет. По факту, в исходниках ядра Linux действительно считается что равны (наверно так исторически сложилось, т.к. в заголовках ядра присутствует тип uintptr_t, который к слову объявлен как unsigned long), соответственно это так для платформ (toolchain+архитектура процессора) на которых.работает Linux.
Стандарт — один из основных документов, которым я руководствуюсь в своей деятельности.

Меня всегда удивляет как «чтение стандарта вслух» вызывает восторженные и удивлённые возгласы «да вы что?», «не может быть!» даже у senior программистов Си.
Далек я от С, но складывается стойкое ощущение что что-то и где-то в нем пошло не так. Ну слишком уж много в нем совершенно базовых особенностей «которые должен знать каждый пишущий на С программист»
Всё в нём так, просто не забывайте его нишу. Си должен быть, с одной стороны, крайне низкоуровневым, а с другой — максимально переносим. Это накладывает свой отпечаток.

Кстати, давайте не будет продолжать оффтоп.
это неизбежная судьба языка, через который можно достучаться почти до аппаратного уровня и многокластерного сервера и бытового холодильника
Это вы еще C++ не видели…
sizeof(char)>sizeof(long)

Нет!
В стандарте сказано, что sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long). Раздел 6.3.1.1.
Спасибо, исправил.
Что ж, это ещё более наглядно показывает, как хитровыкручен стандарт.
Таким образом, вполне законны ситуации типа sizeof(char)>sizeof(long).


Не бывает так.

n1570, 6.2.5p8:
For any two integer types with the same signedness and different integer conversion rank
(see 6.3.1.1), the range of values of the type with smaller integer conversion rank is a
subrange of the values of the other type.


6.3.1.1p1:
The rank of long long int shall be greater than the rank of long int, which
shall be greater than the rank of int, which shall be greater than the rank of short
int, which shall be greater than the rank of signed char.

Да-да, я лох. Не заметил этого пункта.
C переносимый, поэтому в нём базовые целочисленные типы (char, short, int и др.) не имеют строго установленного размера, а зависят от платформы.

То, что C переносимый — это миф. Может раньше он и был таковым, но сейчас — увы. Вышепреведенная фраза очень похожа на следующую: «в целях заботы о пользователях мы поднимаем плату за обслуживание». Спасибо, не надо.

Интересно, много ли на свете программистов, которые хотели бы, чтобы они не знали размеры базовых типов? Наверно, это чтобы было быстро? Т.е. лучше быстро и глючно? Наверно поэтому практически любая библиотека включает в себя configure — чтобы переносилось, ведь это ж часть языка, т.к. язык все может, ведь он переносим, да? А обилие #define тоже наверно говорит о великолепной переносимости? А то, что стандартные библиотеки не могут кроссплатформенно открыть, например, pipe — это тоже говорит о великолепной переносимости. В общем, заявление о том, что для поддержки переносимости нет определенности — это, извините, к доктору. Если хочется переносимости, то тогда надо все четко специфицировать, как, например, в Java. И не надо лохматить бабушку.
ну, программисты знают размеры базовых типов — sizeof в помощь
Как sizeof поможет решить, какой тип использовать? Например, мне нужно проитерировать миллион элементов. Как, используя sizeof и без дефайнов мне написать такой цикл «переносимо»?
size_t — единственный правильный тип индексов.
Согласно стандарту size_t позволяет проитерировать миллион элементов?
Согласно стандарту верно одно из двух: или size_t позволяет проитерировать миллион элементов или такой большой массив не поместится в память вашей машины.
А если данные итерируются на диске или еще где-нибудь? Ведь я не говорил, что данные в памяти.
мне кажется, вы передергиваете понятие переносимости
Из википедии:
Портируемость (переносимость, англ. portability) обычно относится к одной из двух вещей:
1. Портируемость — как возможность единожды откомпилировав код (обычно в некоторый промежуточный код, который затем интерпретируется или компилируется во время исполнения, «на лету», англ. Just-In-Time), затем запускать его на множестве платформ без каких-либо изменений.
2. Портируемость — как свойство программного обеспечения, описывающее, насколько легко это ПО может быть портировано. По мере развития операционных систем, языков и техники программирования, становится всё проще портировать программы между различными платформами. Одной из изначальных целей создания языка Си и стандартной библиотеки этого языка — была возможность простого портирования программ между несовместимыми аппаратными платформами. Дополнительные преимущества в плане портируемости могут иметь программы, удовлетворяющие специальным стандартам и правилам написания (см., например: Smart Package Manager).

Вот теперь мне хочется понять, какой из пунктов для C легко реализуется?
1 — не смешно
2 — реализуется. настолько легко, насколько вообще можно учесть в одном коде огромное количество различий (да-да, в том числе размеров базовых типов) платформ.

если вы ждете такой же «легкости», как у java или чисто интерпретируемых языков, мне жаль вас огорчать.
а вообще azudem ниже прекрасно все расписал
Если данные на диске, то вы ничего не итерируете, а осуществляете random access к файлу. Поэтому по стандарту Си (чтобы быть максимально педантичным отвечая на троллинговый вопрос) вам нужно использовать fpos_t вместе со стандатрными функциями из stdio.h.

Если быть более реалистичным и расширить кругозор до POSIX, то мы увидим off_t.
Ну вот пример: у меня есть файлы на диске file.1, file.2,… file.1000000. Мне надо пройтись по всем файлам и показать контент. Каждый файл, например, занимает 10 байт (хотя это не принципиально). Как это сделать переносимо по стандарту?
Вы пытаетесь придумать уже третий пример на эту тему, но пока получается только fail. Вы не думаете что вам нужно просто почитать стандарт?

В данном случае, так как известно что файлов всегда ровно миллион, я бы применил uint32_t для номера файла. Но в общем случае стоит применить uintmax_t.
что бы работать с данными, их нужно загрузить в озу
на 32битной платформе ты как ни прыгай, но миллиард элементов ты не проитерируешь при помощи size_t эффективно.
Нет особобенных проблем в написании переносимого кода. Даже между очень разными архитектурами (разный endian, размеры базовых типов и т.п.). Утыкивать код кучей #define для этого не требуется.

configure из autotools используется для другого — отключить часть функционала, указать где библиотеки брать и заголовки, куда ставить бинарники.
Си переносим только в плане платформ процессоров.
Си ни в коем случае не является абсолютно переносимым языком. Такого языка не может быть в принципе.

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

1) Интересно, много ли на свете программистов, которые хотели бы, чтобы они не знали размеры базовых типов?

Я один из них. Если мне нужна переменная для содержания любого целого числа от 1 до 100, мне важно лишь, чтобы используемый мной тип это позволял. А способен ли он вместить больше — меня не интересует.

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

Если у меня массив из большого числа переменных и мне нужно минимизировать память, то мне нужен тип, который гарантировано вмещает числа от 0 до 100 и был минимального размера. Из stdint.h я выбираю int_least8_t. (Тип int8_t может быть не доступен на моей архитектуре, например если байты 9-битные; а int_least8_t в этом случае и будет 9 бит).

Если мне нужно обеспечить быструю работу с моей переменной, то я выберу int_fast8_t. Пускай на каком-нибудь DSP он будет 32-битный, но работа с 32-битными регистрами на данной архитектурами будет наиболее эффективная.

2) Наверно поэтому практически любая библиотека включает в себя configure. А обилие #define тоже наверно говорит о великолепной переносимости.

Этим обеспечивается переносимость более высокого уровня, в основном между операционными системами, различными компиляторами и пр. Си это не интересует, его интересует более низкоуровневая переносимость. Он для того и создавался, чтобы заменить ассемблер, когда разработчики затрахались переписывать UNIX, переходя на новую машину.

3) то, что стандартные библиотеки не могут кроссплатформенно открыть

Это проблема библиотек.

4) Если хочется переносимости, то тогда надо все четко специфицировать, как, например, в Java.

Java высоуровневый язык. На нём ОС или драйвер не напишешь (если только не смеха ради).
Безусловно, язык С справляется на 100% если речь идет про драйвера и низкоуровневые вещи. Я про другое. Про то, что на данный момент С является одним из самых непереносимых языков. Наверно, по непереносимости его переплюнет лишь ассемблер. Другие языки хорошо себя чувствуют на разных платформах. Просто С — он всеядный, т.е. доступен на огромном количестве платформ, и это иногда путают с переносимостью. Те же драйвера всегда пишутся под конкретную платформу и ось, что говорит о слабой переносимости. Каждый язык хорош для своих задач. Просто спецификация того, что char может быть 8-битным, а может быть и нет, говорит о том, что язык стараются адаптировать под различные платформы, а переносимость, т.е. портируемость этого кода на другие плафтормы, от этого лишь страдает.
Про то, что на данный момент С является одним из самых непереносимых языков. Другие языки хорошо себя чувствуют на разных платформах.

Вы какую-то чушь написали. Компилятор для языка С для какой-нибудь платформы X — появляется самым первым. Либо это совсем уж экзотика какая-то.
Не могу с вами согласиться. Си предоставляет возможность писать переносимый код, но не обязывает эту возможность использовать. Большинство реального кода пишется под конкретную арлитектуру, зачастую с фиксированной разрядностю. В таком коде могут итерировать массивы int'ом, указатели приводить к long, делать бинарные операции с предположением о 8-битности байта или просто использовать типы со строгим размером типа uint8_t. Си позволяет это делать; кто-то (вроде Томсон) сказал, что Си как лезвие — если им неправильно пользоваться, можно создать кровавое месиво.

Если же писать качественно, с учётом всех возможностей и рекомендаций стандарта, то получить непереносимый код не так-то просто. Даже в контексте рассматриваемой темы, где под переносимость в основном понимается переносимость между процессорами с различной разрядностю и набором регистров, придумать правильно написанный пример непереносимого кода у меня не получилось. Может вы такой пример предъявите, тогда мы перейдем от философствования к предметному разговору.
Автор молодец.

Но вообще грустно очень. Писать переносимо на C/C++ (одна из задач, для которой язык создавался) умеют совсем немногие.
Эх! Помню как убил пару дней отлаживая программу, которая под х86 работала как надо, а под х86_64 — нет!
Оказалось, что:
на х86:
sizeof(int)=4, sizeof(long)=4, sizeof(long long)=8
на х86_64:
sizeof(int)=4, sizeof(long)=8, sizeof(long long)=8

А в коде много было переменных типа long.
Я ответил на все вопросы правильно! Как я так смог? Да в книге Страуструппа все есть =)
Даже то, что char, signed char и unsigned char — Три разных типа!

Особенно 3 вопрос простой — неужели никто в bc 3.1 не программировал с 16 битным int'ом? :) Неужели никто не читал основы такого языков, как Java, который постулирует одинаковый размер int'a в отличие от С\С++ ?!
Какая связь между вопросом 3 и 16-битным int? Я на (3) ответил «не менее 4» (более-менее наугад), а на (4) — «от -128 до 127» — не был уверен, что хотя бы 2 байта гарантируются. Но я стандарта не читал :(
Пардон, конечно же 4-ый вопрос! Видимо, было слегка поздновато, невнимательность!

И отвечая товарищу azudem ниже (из-за кармы приходится группировать сообщения) — да, про подмножества мне очень хорошо известно, любимый пример некомпилируемой программы в С++ и компилируемой в С — int try;
Но тут речь о приемственности стандартов! В возможности С++ так же входит низкоуровневая работа, поэтому почему бы не унаследовать приемы от языка, который себя прекрасно в этом зарекомендовал! И эту строчку со сравнениями я узнаю из книги страуструппа, могу предположить, что она попала в стандарт С++ простым (вдумчивым ?) копированием из стандарта С.
Строчка про sizeof() ни в стандарте C, ни в стандарте C++ не встречается. Но та же информация про размеры типов в стандарте C++ выражена более явно:

[basic.fundamental]p2:
There are five standard signed integer types: “signed char”, “short int”, “int”, “long int”, and “long
long int”. In this list, each type provides at least as much storage as those preceding it in the list.
Про какой sizeof() вы говорите?
Я говорю про habrahabr.ru/post/156593/#comment_5349219

Это правильное и очень красиво записанное утверждение, но этой строчки в таком виде в стандартах нет (а жаль, так нагляднее).
Поздравляю, но не стоит забывать, что Си не является подмножеством Си++.

Про 16-битный int многие знают из практики, но как правило считают, что нижняя граница диапазона на 1 меньше, чем гарантирует стандарт.
Программил на низкоуровневом Си где-то год под различные контроллеры, а доподлинно знал ответы только на 3 и 4 (и то из-за внимательного чтения K&R). Век живи, век познавай. Спасибо за статью, отличная!
А правильно я понимаю, что unsigned char и signed char — это как бы байты и уж как минимум абстракции чисел, а char — это вообще не число, а символ алфавита и может занимать хоть 4 байта?
а char — это вообще не число, а символ алфавита

Вы слишком резко. Все рассмотренные в статье типы предназначены для хранения целых чисел. Их интерпретация на совести программиста. Но, действительно, char является естественным типом для хранения базовых символов.

Точнее, стандарт определяет базовый набор символов (basic character set), указывая минимальные требования к нему (в частности, он должен включать латинский алфавит в обоих регистрах), а тип char должен вмещать любой из базовых символов, причём так, чтобы их числовое значение было неотрицательным. (Конкретный пример: базовый набор — ASCII, а char — знаковый 8-битный целочисленный тип.)

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

Если же говорить о char как просто о целочисленном типе, то стандарт требует, чтобы диапазон его значений совпадал либо с signed char, либо с unsigned char, на выбор компилятора. То есть фактически тип char эквивалентен одному из этих типов, как я уже писал в статье.
А если basic character set — это, например, три миллиона символов, но при этом мы работаем на 8 или 16-битной машине, то тогда int будет обязан иметь размер больше, чем 3 байта?
char в данном случае должен вмещать любой из трёх миллионов символов, а int не может быть меньше char. То есть размер int не может быть меньше 22 бит.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории