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

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

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

Точно, я про calloc забыл, если честно. Спасибо

НЛО прилетело и опубликовало эту надпись здесь

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

Я бы читателям лучше по С любую книжку прочитать бы посоветовал, в которой будет написано, что указатели хранят АДРЕС переменной в памяти, и тогда вопросов про размер указателя, равенство размеров указателей и т.п. не возникло бы) PS. Сам на C не пишу, но стало интересно.

Тогда посоветуй мне ещё что-нибудь про работу оперативной памяти. Мне, когда я заинтересовался управлением памятью адрес НИЧЕГО не говорил. Сколько бы я не читал, мне совершенно ничего не давало это утверждение. И я считаю, что программисту, который не закапывается в ассемблер данное утверждение НИЧЕГО не даёт. Так как адрес памяти мы получаем всё равно от операционной системы.

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

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

Судя по тому, что я видел, когда прощупывал Vulkan, это делают достаточно часто, иначе бы для этого не выделили бы параметр почти во всех функциях.

Вроде ещё что-то было в библиотеке FFTW, кажется… как-то она хитро выравнивает при аллокации, чтобы быстрее работало… или дежа вю у меня уже…

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

А в DOS4GW было «аллокатить в первом мегабайте, будем юзать для легаси-служб DOS и BIOS» :)

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

Без обид, но лучше на Вы, лично не знакомы. Вообще это ни разу не про ассемблер, а про базовые принципы представления программы в памяти. То есть лично для меня это из области "что такое 32-битная или 64-битная ОС". Если пишете на языке достаточно низкого уровня (как С), то предполагается, что базовый курс по компьютерной архитектуре за плечами имеется. Материала в сети сейчас на эту тему масса, хотя для полного понимания я бы посоветовал действительно "закопаться" в ассемблер, в любом учебнике по ассемблеру разжеваны основы архитектуры, в том числе адресация. Я лично не совсем представляю себе программиста на С без знания основ ассемблера. Удачи в свершениях, ждем профессиональных статей на Хабре.

Кстати, я всегда, на каждый пост, получаю от каких-то гениев, которые соизволили прочитать мои записи, несколько минусов за низкий технический уровень материала. Что им не нравится? Я множество раз перечитываю и думаю, что я не так сделал. Убрать приветствие в начале? Убрать заключение? Но так делают везде, где я читаю, значит не то. Тогда в чём проблема? Эти гении молчат как рыбы и не считают комментарии необходимыми. Или на хабре так принято?

Убрать приветствие в начале? Убрать заключение?

Возможно проблема в середине поста, а не в его начале или конце?))

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

Всегда так. Пытаешься писать для себя - нет идей что написать в принципе. Пишешь для других, тебе каждый грех будут припоминать. Указываешь, что пишешь не для гуру Си, они тебе будут тыкать тысячами материалов, говоря о том, что всё уже давно написано. Только когда спросишь, где были эти чудо-материалы, когда сам пол интернета прогуглил в их поиске, находя только призрачные объяснения, которые больше рассказывают о работе ПК, чем о программировании, неловко молчат в тряпочку. Посоветуй новичкам в комментариях материалы, которые стоит прочитать и которые они с ходу поймут! Может я улучшу статью за их счёт?

сам пол интернета прогуглил в их поиске, находя только призрачные объяснения, которые больше рассказывают о работе ПК, чем о программировании

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

Я учил по книге Подбельский, Фомин "Программирование на языке Си", полностью ей доволен, но это было почти 20 лет назад, с тех пор могло появиться что-то получше.

Да не, лучше могло и не появиться. Спасибо за книгу!

Не минусовал и уж точно не гений, но выскажу предположения.

Для начала статья по объему и полноте на руководство имхо, уж извините, совсем не тянет. В статье про память ни разу даже не упомянули стек и кучу, страницы памяти и кеш, не объяснили, как пользоваться и зачем вообще нужны malloc/free и очень-очень много чего еще. Этому явно способствует еще и ваш личный контекст, словно (вы даже это упомянули) пишете только о том, что сами недавно узнали/уточнили, и спешите с кем-то поделиться новостями.

Довольно много неточных/спорных утверждений, а это огромный минус для обучающего материала.

Наконец, для кого вы писали? Из новичков имхо мало кому поможет, слишком обрывочные сведения и неясен порог входа, а ведь в обучении очень важны системность и последовательность. Более опытные не увидят ни новых фактов, ни приемов/практик. Получается, что у статьи толком нет аудитории, ее некому положительно оценить.

Ну и несколько вещей, которые мне кольнули глаз:

Указатели [...]
При этом мы не сами выделяем память, а просим операционную систему выделить память нам нужное количество байтов.

За всю статью об управлении памятью не были даже упомянуты стек и куча - собственно, память программы. Сам по себе указатель не требует выделения памяти через *alloc - у вас ниже даже пример такой есть, с использованием ссылки. Самой кучи, кстати, может вовсе не быть.

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

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

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

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

Указатель - это число с размером разрядности компьютера. Но даже если размер не совпадает с разрядностью, размер указателя точно будет совпадать с размером uintptr_t.

Так размер совпадает или не совпадает с "разрядностью компьютера"?

*pvalue += 1; // обращаемся к значению и увеличиваем на один.
// *(px++) (Или *px++) - это получить значение и сместить указатель на байт.

В отрыве от разбора операций и комбинаций (" px++", "*(++px)", "(*px)++" и т.д.) выглядит заклинанием.

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

packed структуры шляпа потому, что указатели на члены такой структуры ничем не отличаются от прочих указателей, но при том обращение по таким указателям (не выравненным) может потребовать специальных машинных инструкций.

Для тех кто любит говорить, что мол на x86/arm/etc... всё можно невыравненно, напоминаю, что векторные инструкции часто в таких случаях таки вызывают исключения, а их может компилятор вставить в совершенно безобидном месте просто для того чтоб быстрей скопировать данные (memcpy заинлайнился). Компилятор знает, например, что структура или int всегда выравненный и будет копировать его векторными инструкциями не глядя. В рантайме всё грохнется.

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

Спасибо, учту. Думаю я даже могу найти применение этому теперь.

Минимально-адресуемая единица таки "char" -- по стандарту. Другое дело, что это у компилятора так, а у процессора может оказаться как вы описываете. А у некоторых CHAR_BIT просто равен 16 (DSP фирмы Texas Instruments).

@stalker320, пожалуйста, удалите упоминание меня из статьи :) ну или хотя бы давайте уберем или поправим те пару абзацев про стек и кучу, а то вы хоть и молодец, что трудитесь над исправлением/дополнением, но все же странных вещей написали.

WARNING: написанное я все равно не стал бы включать в статью, т.к. сильно упрощал и оставил много неточностей. Это слишком обширная и сложная тема.

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

  2. Про кучу и динамически выделяемые данные не очень понятно получилось. Данные-то вполне могут быть динамическими, но размещаться в стеке, а важно тут выделение области памяти: стек ее получает статически, однократно при запуске программы, а куча может меняться в процессе работы программы, сколько операционка позволяет процессу: нет в уже имеющейся памяти непрерывного куска нужного размера - запросили у операционки (на самом деле тут одновременно и сложнее, и проще, т.к. это виртуальная память) еще кусок памяти и продолжаем в ней сами себе выделять области - этим занимается рантайм языка. И наоборот, когда память освобождается, то она может вернуться операционке. За счет этих манипуляций выделение памяти в куче медленее, чем на стеке. "Куча может в программе и не появиться" - это все-таки контролируется вручную, т.к. такое ограничение могут накладывать целевые платформы - микроконтроллеры, например. Еще из важных отличий, что в куче можно хранить куда бОльшие объемы данных (стек же маленький) и ее можно шарить между потоками (у потоков собственные стеки).

  3. В очистке памяти вы описали затирание данных с точки зрения безопасности, а не возвращение в кучу, что, собственно, в контексте управления памятью обычно очисткой и называется. Вот вы запросили в куче память, получили указатель. Куча у себя отметила, что область с N по N' занята, ее больше нельзя использовать при последующих запросах. Когда эта область перестала быть вам нужна, куча-то все еще считает, что память занята. А если вы даже и указатель не сохранили, то получается классическая утечка памяти: область занята, но у нас практически нет возможности это понять и ее освободить для дальнейшего использования, она так и будет висеть мертвым грузом до завершения процесса. Вот для этого возвращения и нужна очистка через free: куча отмечает область свободной и может вам в дальнейшем снова выдать в ней место даже без обращения к операционке.

Подобрал несколько хороших ссылок по теме
https://habr.com/ru/post/270009/
https://habr.com/ru/post/345766/
https://habr.com/ru/post/489360/
https://www.ibm.com/docs/ru/aix/7.1?topic=concepts-system-memory-allocation-using-malloc-subsystem

  1. То есть адрес указателя int* ptr; будет храниться в стеке, в то время, как в хипе будут храниться сами данные?

Именно. Если подробнее, то операции и их порядок примерно (я все же тут не эксперт) таковы:

int (int n) {
	int * ptr = malloc(sizeof(int));
	*ptr = 1;
	free(ptr);
	return n+1;
}
  1. На входе в контекст (грубо говоря, в функцию) в конце стека выделяется место под n, возвращаемый результат и ptr, как если бы все объявления переменных были смещены в начало функции и не имели инициализации значением. В аргумент же автоматически копируется значение из места вызова. До самого конца размер стека не меняется. И этот пункт стоит уточнить, поскольку аргументы функций (первые 4, кажется) и возвращаемые значения могут передаваться даже не через оперативку, а через регистры процессора.

  2. У кучи запрашивается непрерывный кусок памяти размера sizeof(int). Если в свободных областях есть такое место, то куча его отделит по размеру, пометит занятым и отдаст указатель на начало области. Если же сводного места нет вовсе, или есть, но оно фрагментировано, т.е. состоит из нескольких частей меньшего требуемого размера и не идущих подряд, то куча сообщает операционке, что увеличивает свой размер. Операционка в свою очередь проверит, не превышает ли процесс заданный лимит на память, и подыскивает в физической памяти кусок нужного размера. Тут надо отметить, что память процесса - виртуальная, "лоскутная". Операционка сшивает несколько разных областей памяти, которые могут быть даже на разных типах устройств, и создает новое адресное пространство, которые выглядит цельным и непрерывным для процесса. Куча поэтому просто сообщает операционке, что хочет вырасти на определенный размер, а операционка подшивает к виртуальной памяти еще один кусок физической. Получив еще памяти, куча теперь может выделить запрошенный через malloc кусок или бросить исключение, если не получилось.

  3. В стековую переменную ptr записывается адрес начала выделенной кучей области.

  4. Из стековой переменной ptr считывается хранящийся там адрес в куче.

  5. По считанному адресу в куче записывается интовая единица.

  6. Куча получает запрос на освобождение: она помечает у себя, что область, начинающаяся с ptr, теперь свободна и снова может быть использована. Если есть соседние свободные области, то куча их объединит в одну большую, а при определенных условиях может еще и память операционке вернуть (тут уже подробностей совсем не знаю). На каждый malloc со временной выделенной областью должен приходиться ровно один free с тем же адресом.

  7. По адресу возвращаемого результата на стеке записывается интовое n+1.

  8. На завершении контекста у n и ptr вызвался бы деструктор, не будь они базовыми типами.

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

Благодарю.

Размер указателей не всегда одинаковый — на некоторых платформах на размер указателей могут влиять модификаторы модели памяти и адресного пространства. В частности, на x86 в реальном режиме на размер указателей влияют модификаторы far и near, в зависимости от которых, а также от модели памяти по умолчанию, указатели могут быть 16-битными или 32-битными. Аналогично, в 32-разрядных программах на 64-разрядных x86-процессорах могут использоваться 64-разрядные указатели. А на платформах с Гарвардской архитектурой есть еще и отдельные типы указателей на память программ (в которой также могут храниться и константные данные), которые тоже могут быть разного размера. В частности, на микроконтроллерах AVR указатели на память программ могут быть 16-разрядными и 24-разрядными.

Спасибо за уточнение. Но можно уточнить у вас, как у знающего?

В частности на микроконтроллерах AVR указатели на память программ могут быть 16-разрядными и 24-разрядными.

В пределах одной программы 16-разрядные и 24-разрядные одновременно? Или есть константный размер указателя, который определяется при запуске из операционной системы, 16 разрядов, или 24 разряда?

Размер указателей и, соответственно, доступная область размещения в памяти программ по умолчанию определяется опцией компилятора «модель памяти». Эта опция может быть разной для разных объектных файлов в пределах одной программы. Также, размер указателей может быть переопределён для конкретной функции, константы, или указателя. Компилятор IAR использует для этого ключевые слова __flash (16-битный указатель на данные или размещение данных в сегменте, на который может указывать 16-битный указатель), __farflash (23-битный указатель на данные, или соответствующее размещение данных) и __farfunc (23-битный указатель на функцию, или соответствующее размещение функции). На практике это обычно не используется, но может быть использовано для ускорения работы программы, и уменьшения объёма памяти под указатели. Дополнительно, у IAR есть еще тип 24-битных указателей, который может указывать и на память данных, и на память программ — они объявляются с ключевым словом __generic.

Про разный размер указателей для разных типов тоже хотел указать, в sdcc man есть пояснения по этому поводу про memory map model

С одной стороны, молодому поколению надо регулярно напоминать, как ещё можно писать, кроме «скачай моднявенькие фреймворки и соедини блоки мышкой» (а сейчас уже скорее «спроси у ЧатГПТ»). А то «Блокнот» в 3.5 гигабайта весом, совершенно на серьёзных щах поданный почтеннейшей публике (и ведь кто-то им восхищается!) — это, извините, конец света, кровавый дождь, саранча и горящие собаки с неба.

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

Ну и про указатели правильно заметили, не хватает их «сути». Это может быть вообще нечто страшное, например, в случае сегментированной модели памяти (мало ли где на микроконтроллерах человек снова нарвётся на это наследие 8086-го).

Разница malloc/calloc, кто для каких случаев лучше.

Фрагментация кучи, реаллокация (не путать с :) ). Страницы и маппинг их на физическую память.

Byte order — интеловский (3 2 1 0), мотороллерный (0 1 2 3), костыльный (2 3 1 0). Там, где упомянут union — нельзя обойти этот аспект вниманием.

Короче, подсказал что в голову первое пришло («чем смог — помог, картошку сажайте сами» © анекдот), но поле тут непаханое, писать и писать.

Кстати, что за "костыльный" Byte order и причём тут union? (Вроде это же больше со struct связано)

Где-то натыкался… Википедия только PDP-11 называет в этом качестве, но это явно был не он :)

А с union всё просто — если надо структуру типа «четыре U32» по какой-то причине (обычно это скорость где-то в глубине цикла в цикле в цикле, что ж ещё…) интерпретировать как «шестнадцать U8», то просто pragma pack будет недостаточно — надо предусмотреть вариант кода для интеловской и мотороллерной эндианности, ну и как минимум выплюнуть ошибку в случае экзотики :)

А, точно, как же я забыл pragma pack упомянуть сразу! Там, где речь идёт о структурах и объединениях и тема коснулась их расположения в памяти — это ж главное блюдо на всём обеде!

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

UPD: «костыльных» (смешанных) может быть несколько, у PDP вроде был другой вариант.

интеловский получается LITTLE_ENDIAN(..., 2^3, 2^2, 2^1, 2^0), а мотороллерный - BIG_ENDIAN(2^0, 2^1, 2^2, 2^3, ...), если я правильно понял, что ты имел в виду.

Кстати, не в курсе про htonl()htons()ntohl() и ntohs() . Я просто видел упоминания вместе с byte order, но так и не понял зачем нужно и как использовать.

В TCP/IP применяется порядок байтов big-endian. Вышеперечисленные функции нужны для того чтобы писать кросплатформенный код. На big-endian машинах они будут просто возвращать значение.

А, понятно. Спасибо.

"костыльный" был на x51 помнится. причём там ещё и костыли в разные стороны, в зависимости от того в какой памяти лежало "длинное" число (в idata или xdata).

нечто страшное, например, в случае сегментированной модели памяти (мало ли где на микроконтроллерах человек снова нарвётся на это наследие 8086-го).

Мне постоянно кажется, что многие люди путают или не видят разницы между сегментами 8086 и сегментами 80286 или тем более 80386.

А точнее сказать, между сегментами реального режима и сегментами защищённого режима.

Сегменты защищённого режима это, это по-моему глубокому убеждению, технология с самым underrated потенциалом развития, которую толком по уму никто не использовал из-за желания обратной совместимости с одной стороны и отсутствия ЯВУ, который мог бы эксплуатировать идею сегментов максимально естественным образом. Доминировавшие на тот момент Си и Си++ были явно не теми языками из-за своей адресной арифметики.

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

И увы, человечество предпочло свернуть не туда, и теперь мы имеем атаки переполнения буфера, remote code execution и костыли в виде ASLR и DEP для борьбы с ним. Хотя могли бы иметь защиту от доступа за границу объекта/массива/буфера на аппаратном уровне. Ну и проблему с фрагментацией АП процесса, которая становится теперь головной болью прикладной программы, а не ОС.

НЛО прилетело и опубликовало эту надпись здесь

Учту при редактировании. Спасибо за ссылку

И кстати, разве ссылка не на статью по C++? Или в C правила те же?

НЛО прилетело и опубликовало эту надпись здесь

Благодарю

Многое в общем-то не соответствует действительности...

размер bool = 1 байт

Во-первых размеры bool, int и т.п. -- платформенно-зависимые. Есть где bool -- 4 байта как и int (MIPS).

все указатели имеют одинаковый размер, а значит их можно относительно безболезненно преобразовывать между собой

Нельзя. По-стандарту нельзя указатель на функцию приводить к void* и наоборот. На практике можно, но не всегда заработает. И нельзя сопоставлять указатели на разные объекты. Разность указателей можно считать только в пределах одного объекта (одного аллоцированного фрагмента памяти). Для большинства архитектур это не принципиально, у них "плоская" модель памяти, но не у всех.

символ со знаком -0x7F равен символу без знака 0xFF

Полная чушь, 0xff -- это -1 везде, где отрицательные числа представлены в дополнительном коде (на современных архитектурах -- везде).

Размер же структуры - это сумма размеров её полей, выравненных по байтам.

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

поля объединений начинаются из одной точки и имеют размер наибольшего элемента...

Поле объединений имеют размер этих полей, и он разный для разных полей. Одинаковый они имеют только _Alignof().

Рассказывать о перечислениях(enum) Нечего, потому что это массив чисел, который компилятор удобно подписал ключевыми словами. Не хуже справляется команда препроцессора #define.

Это совершенно разные сущности. enum -- это способ определить константу типа int во время компиляции в языке C. Единственный. А #define -- подстановка текста. Во что она там ещё превратится -- вопрос. Как минимум типы могут получиться разные. И результат.

Указатель - это число равное, или меньшее размером, чем разрядность процессора(Большее число просто не выйдет просчитать так же эффективно).

Ну да, конечно. Особенно на 8-битных микроконтроллерах или даже на 16-битных (типа dsPIC), или даже на 16-битом x86 с сегментной адресацией...

также в другой части есть стек и куча, что растут по направлению друг к другу.

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

Массивы в Си - это указатели на области памяти...

Массивы -- это отдельный тип данных, а не указатели. Массивы лишь деградируют (decay) до указателей в определённых выражениях (но не во всех: операторов sizeof, typeof и & это не касается). И массив как тип данных имеет в первую очередь размер, что позволяет резервировать под него память. А указатели -- все на один размер.

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

Многомерный массив НЕ ЯВЛЯЕТСЯ массивом указателей. Это справедливо только для "char *argv[]" из аргументов main, но строго говоря там не многомерный массив, а вполне себе одномерный массив указателей на строки. Вычисление адреса в многомерном массиве не задействует никакие указатели и зависит от размерностей (всех кроме последней) массива. Для массива указателей же как раз размерность не важна (почему в argv строк может быть сколько угодно и каждая любой длины).

И да, массивы с динамической размерностью (variadic length arrays) как раз в C возможны (но не в C++). Сомнительная вещь, но что есть то есть.

В остальном статья очень плоха. Не стоит о чём-то писать, о чём не имеете представления. И уж тем более не стоит читать российскую литературу. Керниган и Ритчи -- устарели. Возможно просто стоит изучить сайт cppreference.com (там есть раздел описывающий язык C). Если учить, то скорей Роб Пайк, "Практика программирования".

PS: примеры на C откровенно плохие, начиная с не использования const, передачи структур то по-значению, то по-указателю, код в целом низкого качества. В случае с вектором лучше возвращать "ссылку" на элемент, чем его значение (тогда по ссылке его можно изменить). Кастить void* к указателю на конкретный тип и наоборот в C не нужно (в отличии от C++)...

В случае с вектором лучше возвращать "ссылку" на элемент, чем его значение

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

В статье очень много такого, что справедливо лишь для систем принстонской (aka фон Неймановской) архитектуры с плоской (flat) адресацией, не требующей выравнивая (alignment) данных. Этакая сферическая amd64.

Тип bool давно уже существует. Только называется в языке C — _Bool. И работает без всяких “подключений стандартных библиотек”.

Размеры всех типов (кроме char, который всегда 1, сколько бы байт он реально ни занимал) зависят от платформы и реализации. Система, у которой sizeof(char) == sizeof(short) == sizeof(int) == sizeof(long) == 1 — не фантастика и не бред. Это про платформу. А про реализацию другой пример: на одной и той же платформе amd64 sizeof(long) == sizeof(int) в одной очень популярной реализации C/C++, и sizeof(long) == 2 * sizeof(int) в другой не менее популярной реализации.

Переменная типа bool не перестаёт быть переменной. А у переменной может быть взят адрес. Поэтому тип bool ни при каких обстоятельствах не может быть меньше, чем минимальная адресуемая сущность на конкретной платформе. Не слышал ни об одной, где можно было бы взять адрес бита в байте/слове/d-слове/q-слове/...

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

В компьютере с гарвардской архитектурой размеры указателей на данные и указателей на функции могут очень сильно не совпадать
Более того, размеры указателей могут не совпадать и в компьютерах с фон-Неймановской архитектурой. Так, в x86 есть отдельное адресное пространство ввода-вывода. А некоторые компиляторы имеют атрибуты, позволяющие определить указатели не только на память программ и память данных, но и на порты ввода-вывода в отдельном адресном пространстве, и на встроенную в микроконтроллер память EEPROM. Мне неизвестно, есть ли такие компиляторы для x86, но теоретически такое вполне возможно.

Ответ: булевой переменной в принципе не существует без подключения стандартных библиотек. А при подключении библиотеки stdbool.h, размер bool = 1 байт

Что это за чушь?

В С89/90 действительно не существует булевского типа и никакое "подключение стандартных библиотек" здесь не поможет.

Начиная с C99 в языке С есть булевский тип _Bool. Он является частью ядра языка. Никакое "подключение стандартных библиотек" здесь не нужно. Заголовок <stdbool.h> лишь определяет некоторые макро-псевдонимы.

И что вообще такое "стандартные библиотеки" - именно во множественном числе? В языке С есть только одна стандартная библиотека.

Использовать предельно аккуратно, так как при преобразовании указателя, данные внутри не преобразовываются: символ со знаком -0x7F равен символу без знака 0xFF из-за того, что самый большой бит ответственен за знак. А если мы преобразовываем указатель с размером типа 2 байта, в указатель с размером типа 1 байт, мы гарантированно получим только первую половину данных, если считать от адреса указателя.

Что это? О чем это??? O_o

int* a = (int*) malloc(sizeof(int));

Явное приведение типа применено к результату стандартной функции выделения памяти... Что это за пионэрство?

Массивы в Си - это указатели на области памяти, которые имеют N размеров типа данных (N * sizeof(type))

Что? Постарайтесь запомнить раз и навсегда: массивы в С - это НЕ указатели. Сколько уже сил потрачено на то, чтобы развеять этот пионэрский морок. Но он все вылазит и вылазит откуда-то...

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории