Pull to refresh

Comments 49

UFO just landed and posted this here

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

Очень хорошее замечание. Совсем не вспомнил про это в процессе написания статьи.
На самом деле, это обыденная вещь. Минимальный размер важен, так как хотим попадать в кэш. Но и не забываем про то, что процессору удобнее в выровненными под машинное слово полями работать. К тому же, иногда требуется отображать структуры из CPU на GPU, там тоже свои подвохи есть.
Так что надо будет обязательно это добавить)

Можно тогда ещё написать про прикольные вещи, вроде того, что если у вас часть полей в структуре постоянно меняется (счётчик например), а часть почти всегда только читается, то лучше меняющиеся поля объявлять так, чтобы они попадали в другую ячейку кэша.

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

Есть мааааленькая такая проблема. malloc()/calloc()/realloc() и обертки над ними не являются явными аллокаторами памяти. Внутри системных либ (aka libc/glibc/...) они реализуют собственный пул страниц памяти (лишь частично пред-аллоцированных) и их подкачку. Для конечного потребителя это сопряжено с такими проблемами, как:

— отсутствие физической непрерывности аллокаций (у GPU собственный MMU и ему до фонаря логически непрерывные блоки, памяти аллоцированные системной библиотекой — GPU DMA их проглатывать будет с болью);
— встроенный и зачастую не подконтрольный фрагментатор из коробки;
— lazy MMU allocations.

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

P.S. Не стоит забывать про бесплатный alloca() для незначительных одноразовых действий.

Что вы понимаете в первом пункте под ' отсутствие физической непрерывности аллокаций'?
Имеется ввиду, что реально в ОЗУ данные не подряд лежат? Если я правильно понял, когда мы работаем скажем с OpenGL, Vulkan, нам так и так приходиться пересылать данные с RAM на VRAM. Если оперировать в этих категориях, то контролировать это процесс более мы не можем, разве что минимизировать кол-во пересылок. На этом фоне, думаю, мы можем пожертвовать физической разрозненностью, хоть это и проблема, но не такая критичная, как частая пересылка данных. (если вы об этом, конечно)

В первую очередь стоит отметить, что тема статьи не ограничивается графикой. Возвращенный malloc()-ом буфер в общем случае представляет собой фрагментированный набор физических страниц RAM. При попытке доступа к нему со стороны периферии потребуется либо копирование, либо пачка независимых DMA-транзакций, инициированная в любом случае не оптимально (поскольку последовательно и со стороны CPU).

Если же говорить об opengl, сразу оговорюсь что win мне профессионально не интересен. Mesa, например, для аллоцирования использует спец. интерфейс ядра ОС, включённый в DRM. Собственно у DRM есть открытый пользовательский интерфейс libdrm, пригодный в том числе для аллокаций памяти и оборачивания поверх них произвольных практик менеджмента памяти. Vulkan в этом смысле работает через те-же интерфейсы.
Неплохо бы приложить ассемблерный код менеджера памяти. Желательно рабочий, а нарытый с интернета.
Я бы и рад приложить код на asm, да вот только не очень много опыта в написании программ именно на этом языке. На С/С++ реализация, которую лично делал, не достойна внимания, так как код в целом не очень документирован и презентабелен.

Приложил ссылку на стороннюю реализацию, так как это 1) хороший пример, заслуживающий упоминания 2) это показывает, что тема развита, и можно в интернете еще много реализаций аллокаторов на C++ найти :)

Куча мелких неточностей. Вот выхвачу одну, например:
“Unique pointer
Позволяет иметь в программе только 1 указатель на объект (уникальность доступа).“


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


Ошибки все некритичные, но царапают глаза.

Безусловно, согласен с вами.
Под 'иметь только 1' я понимал именно работу с этим unique ptr. Никто не сможет запретить вам из сырого C ptr сделать несколько unique ptr и выстрелить себе в ногу (обязательно это добавлю).

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

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

Согласен. Но видел варианты, где сырой поинтер передавали. Где-то АПИ сторонних библиотек это требовало, где-то АПИ методов подразумевало корректность nullptr как входного параметра, где-то просто ссылки недолюбливали по какой-то причине:)
GC скрывает прямую работу с памятью, зато вы начинаете бороться с GC: вроде тяжёлая таска окончилась, почему же наш кластер малодоступен и нагружен ещё минуту? Ах да, у нас запустилось 600 GC со средним временем работы в минуту. И в этот момент память вроде есть, но её ещё нет.
Вообще, на мой взгляд, проблемы при работе с памятью — один из самых трудноотслеживаемых и проблемных багов. Ещё хуже только получить проблемы при работе с памятью при доступе из нескольких асинхронных потоков.
Да, в проектах типа "Minecraft" работа GC может быть и незаметна

Это только пока игрок не зайдёт на действительно крупный сервер и не сядет в метро :-)

А как давно и в какой конкретно ОС malloc/free стали системными вызовами? По-моему, они таковыми не были ровным счётом никогда. Или я ошибаюсь?
Сами по себе — это библиотечные вызовы (с этим и правда косяк).
Однако в конечном счете получить память для процесса все равно необходимо, поэтому системный вызов сделать хоть раз придется. А теперь представим, что это случилось не в подходящий момент — вуаля, все так же плохо :)

Но чисто теоретически brk/sbrk/mmap/etc ты можешь сделать ровно один раз при старте приложения и потом делить кусок аллокатором как пожелаешь. В контексте лимитов на платформу, озвученных в тексте выше это ещё и выглядит довольно обоснованно.

Да, в проектах типа «Minecraft» работа GC может быть и незаметна
Позволю себе не согласиться. Как раз таки в Minecraft, где могут регулярно сравниться и деспавниться огромные множества разнотипных блоков и объектов, влияние GC может быть очень заметным. Особенно в мутиплеерном режиме, с модами, и при большой дальности загрузки. И тем более для сервера, который вынужден держать в памяти и модифицировать области карты вокруг каждого из игроков.
Не сложно придумать ситуацию, в которой Майнкрафт начнет тормозить.
Изначально, когда вы запускаете игру из коробки (как это было давно давно), модов, шейдеров у вас нет. Разумеется, вы все это добавляете и благополучно нагружаете систему.
Посыл был не в том, что на Java Майнкрафт не тормозит, а скорее в том, что на Java можно и нужно делать игры, если требования по производительности это допускают. Если бы в Майнкрафт была четко сформулированная функциональность и проработанная архитектура, то проблем было бы меньше.
Не сложно придумать ситуацию, в которой Майнкрафт начнет тормозить.
Так речь не об особых ситуациях или торможениях. Речь о том, что Minecraft активно насилует GC из-за того что у него вся карта снизу доверху динамична, и эта особенность активно используется в рядовом геймплее, даже без модов. Конкретно в частоте и количестве выделений и освобождений объектов Minecraft вполне себе сопоставим со средними AAA-проектами, если не превосходит их. Поэтому изначальное утверждение из статьи про влияние GC там и там кажется мне не совсем верным.
Это проблема не столько Майнкрафта, сколько создателей проекта, которые так и не определились, что это должно быть: телега для модов или нет. Безусловно, любая игра с динамической подгрузкой локация будет насиловать runtime, однако если это не критично, то стоит на это забить) Смотрел, как ребята делаю инди проекты, хорошие игры с 3D для десктопа, и средняя джава с 2GiB памяти спокойно их запускает

Наверное, лучше было на java пример с шахматами приводить, тогда точно бы проблем не было)
nullptr или просто 0x0 входит в множество значений, принимаемых указателем, что не есть хорошо, когда особое состояние объекта выражается через его обычное состояние. Это некоторе legacy, и по договоренности ОС не выделит вам участок памяти, адрес которого начинается с 0x0.

Щито? Смешались в кучу кони-люди.


  • nullptr может иметь любое значение и это дело компилятора, а 0х0 тут не при чем Вот у Microsoft тем вообще 0xffffffff если мне память не изменяет.
  • какое "обычное" состояние у указателя если ему присвоили nullptr? Если указатель равен nullptr то это и есть особое состояние, специально введённое в языке чтобы отличить указатель-не-указывающий-на-что-то от указателя-на-что-то.
  • при чем тут выделение указателя на память от ОС и nullptr? ОС вам легко вернёт (в теории) указатель на 0х0, но от этого указатель в вашей программе не станет неожиданно == nullptr
  • сравнение с 0 указателя в C++ опять же просто синтаксический сахар, который вынуждает компилятор знаменить его на присваивание nullptr
    Вообще вся статья о смеси С и плюсов, при этом в тех аспектах где языки ведут себя по разному и дают разные гарантии. Очень сумбурно, по верхам, а местами не верно.
Не берусь утверждать, как работает компилятор на платформе от Майкрософт, однако clang на MacOS дает следующее: void* p = 0; void* d = nullptr; p == d? (it is true). Получается что 0 — это и валидное значение, и одновременно null идентификация. Плюс, насколько я понимаю, заявляется возможность линковки с С библиотеками, следовательно передавая nullptr как аргумент вызова любой функции, это как никак должен быть 0, в С ведь nullptr просто нет.
(поправьте меня, пожалуйста, если с чем-то наврал)

Я об этом и говорю. Сравнение/присваивание нуля в C++ есть синтаксический сахар, который компилятор знаменит на аналогичную операцию с nullptr.
В C есть NULL, который тоже совсем не обязательно имеет численное представление 0x0 и его реализация, как и nullptr, зависит от компилятора.
Все эти хитрости нужны в том числе и для того чтобы избежать привязки отдельной сущности языка (указания на "ничто") к каким-то конкретным адресам памяти.

В старом стандарте C++ присвоение указателю 0 было специально оговорено и означало невалидный адрес, проще говоря NULL. В новых — специальным ключевым словом nullptr. Строго говоря, битовое значение nullptr может быть любым, в отладочных сборках обычно не 0. И это дело компилятора обеспечить корректное сравнение указателя на равенство/неравенство nullptr. При линковке объектных файлов и библиотек, собранных с разными флагами могут возникнуть проблемы. Вроде как.
О, пока писал, уже отписали))
Строго говоря, битовое значение nullptr может быть любым, в отладочных сборках обычно не 0

Это где такое, хотелось бы узнать? И с каких пор null перестал быть integer constant expression with the value 0?
Ну, во-первых integer expression that evaluates to zero, а не with value 0. Но это так, для точности.
А если серьезно, то это константа NULL.
Все это относится к языку и исходному коду. Вы должны инициализировать указатель нулем и сравнивать его с нулем. А компилятор должен в этих случаях генерировать правильный код. А вот какой — это зависит от реализации и от платформы. Вот цитата из C FAQ:
The internal (or run-time) representation of a null pointer, which may or may not be all-bits-0 and which may be different for different pointer types. The actual values should be of concern only to compiler writers. Authors of C programs never see them, since they use.

c-faq.com/null/varieties.html
Q 5.13
Вы же не думаете, что указатель на функцию-член может быть равен 0 какого-либо целочисленного типа? А присвоить ему можно и NULL, и nullptr.
Вы сейчас об очень глубоких слоях абстракции говорите. Чисто технически да, можно представить себе что язык C описан для некоторой виртуальной машины (в которой NULL есть указатель на 0) но физическая реализация этой машины отличается. Но у программиста нет вообще никакого доступа к такой «физической» машине, ее наличие или отсутствие никак не может на него повлиять.

В качестве наглядного примера — назовите мне примеры реальных платформ где memset(0), т.е. прямая и широко используемая на практике операция над представлением данных в памяти не породит NULL pointer. При том что чисто формально такое и возможно.

Вы же не думаете, что указатель на функцию-член может быть равен 0 какого-либо целочисленного типа?

Строго говоря pointer to type и pointer to member type — это две разных сущности в Стандарте хотя и то и то вроде как «pointer». Я полагал что мы говорим о pointer to type. Но если хотите обсудить pointer to member type, то в Стандарте прямо оговорено что присвоение такому объекту nullptr порождает специальную сущность null member pointer value. Т.е. мы не присваиваем nullptr указателю на функцию-член класса, мы приводим nullptr к специальному значению которое отличается от nullptr.
Но если хотите обсудить pointer to member type, то в Стандарте прямо оговорено что присвоение такому объекту nullptr порождает специальную сущность null member pointer value. Т.е. мы не присваиваем nullptr указателю на функцию-член класса, мы приводим nullptr к специальному значению которое отличается от nullptr.

Раз вы теперь про C++:
Все намного проще — есть требование стандарта чтобы присвоение nullptr указателю определяло его как сущность языка null (member) pointer. А как это представлять компилятору это дело компилятора.
Есть требование чтобы присвоение 0 вело себя точно так же как и присвоение nullptr(в целях синтаксического сахара по сути). А ноль или 42 записать при этом в указатель это дело компилятора.
Есть требование чтобы сравнение с nullptr было true если указатель является null (member) pointer. А что лежит при этом в самом указателе это снова дело компилятора.
Есть требование по приведению к целочисленной константе 0 для nullptr, но что лежит "внутри" nullptr — это снова дело компилятора. И т.д.

То о чем Вы пишете было введено для совместимости с экзотическими архитектурами, где указатели на разные типы данных могут указывать на разную память. Там да, может существовать несколько разных null pointer-ов. Скажем int* (0) — один указатель, float* (0) — другой и т.д. Только Вы давно в подобным в реальной жизни встречались? А для архитектур где адреса в памяти можно однозначно ассоциировать с целыми числами NULL вполне однозначно ассоциируется с 0. Хотя чисто номинально таки да, компилятор вправе втихую от пользователя проводить любую другую ассоциацию. Но по факту это бессмысленно и подобные реализации мне не встречались (хотя было бы любопытно о таковых послушать).

Ну так тогда и пишите не на столько категорично, раз у вас речь только о том что вам встречалось, а не о стандарте языке:
"В известных мне архитектурах", "в популярных реализациях" и т.п.


Только почему вы защищаете при этом оригинальный текст, где речь автор явно ведёт о "C++ вообще" и допускает при этом фактические ошибки мне не понятно.

Я просто полагаю что знание о защите нулевой страницы памяти или понимание того как интерпретировать в дизассемблированном коде инструкции вида jz / jnz для современного программиста гораздо полезнее и релеватнее знания о существовании экзотических архитектур (даже названий которых никто не может вспомнить) где были разные типы указателей на разные данные, и для поддержки которых Стандарт попытался адаптировать когда-то простое и понятное определение (введенное для отнюдь не экзотической PDP-11) в нечто совместимое с подобными монстрами.
Нулевая страница памяти относится только к машинам со страничной организацией виртуальной памяти и с MMU. Так что вообще не аргумент. Передо мной на столе прямо сейчас лежит пяток 32-битных архитектур, и в каждой адрес 0x00000000 вполне себе валиден, хошь пиши, хошь читай. Иди ж ты, а это оказывается экзотика! Удивительное рядом.
Даже с учетом того что не везде есть MMU и не везде есть защита нулевой страницы, информация о том что такое решение есть и широко применяется все равно полезнее информации о существовании когда-то давно разной экзотики и о ньюансах введенных в стандарте для ее поддержки. Да и NULL-то я полагаю на этом пятке все равно 0x00000000?

Информация о защите нулевой страницы памяти полезна, но при чем тут язык программирования C или язык C++? Разве что упомянуть что данные языки от этого абстрагированны и ничего ни о какой защите страниц не знают.

Единственная причина защиты нулевой страницы памяти — это то что там находится адрес соответствующий NULL и адреса производные от NULL.
nullptr может иметь любое значение и это дело компилятора

Это не так. Гарантируется что NULL — это 0. К примеру (C99)
An integer constant expression with the value 0, or such an expression cast to type
void *, is called a null pointer constant.
If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function

В С++
The pointer literal is the keyword nullptr. It is a prvalue of type std::nullptr_t. [ Note: std::nullptr_t is a distinct type that is neither a pointer type nor a pointer to member type; rather, a prvalue of this type is a null pointer constant and can be converted to a null pointer value or null member pointer value

A null pointer constant is an integer literal (2.14.2) with value zero or a prvalue of type std::nullptr_t

Вот у Microsoft тем вообще 0xffffffff если мне память не изменяет.

Вы полагаю путаете две совершенно разные вещи — nullptr и non-initialized memory. Non-initialized memory у MS заполняется константами типа 0xBAADF00D (подробнее

ОС вам легко вернёт (в теории) указатель на 0х0, но от этого указатель в вашей программе не станет неожиданно == nullptr

Станет. Есть (в числе прочего) отдельное соглашение по которому страница VRAM содержащая адрес 0 резервируется так что любой доступ к ней порождает SEGFAULT. Это сделано специально чтобы отлавливать доступ по указателю 0 и доступ по смещению от указателя 0.

сравнение с 0 указателя в C++ опять же просто синтаксический сахар

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

Вы сами же приводите цитату где сказано что сам символ NULL определяется как целочисленная константа 0, либо её каст к void. И в следующем же предложении сказано что указатель которому присвоили NULL гарантированно не будет равен ни одному другому указателю, что не отменяет того факта что содержаться в таком указателе может что угодно компилятору.
Не говоря уже о том что на уровне ОС нет никаких гарантий, что указатель инициализированный NULL будет иметь значение 0. И тем более никаких гарантий что разыменовывание такого указателя приведет к segfault к примеру.
Адрес памяти* с значением 0 не то же самое что указатель с присвоением ему NULL.
И последнее-то что генерирует компилятор это его личное дело в данном случае, пока он не нарушает стандарт. Довольно абсурдно приводить вывод компилятора когда мы говорим о абстрактных понятиях стандарта языка.

И в следующем же предложении сказано что указатель которому присвоили NULL гарантированно не будет равен ни одному другому указателю

… ни одному другому указателю на валидный объект или функцию. Проще говоря язык гарантирует что по адресу *NULL никогда не будет лежать ничего валидного. Что позволяет компилятору в некоторых случаях делать какие-то оптимизации, но не позволяет утверждать что NULL может быть интегральной константой отличной от 0.

что не отменяет того факта что содержаться в таком указателе может что угодно компилятору.

Факт в том что текст Стандарта определяет NULL как An integer constant expression with the value 0.

Не говоря уже о том что на уровне ОС нет никаких гарантий, что указатель инициализированный NULL будет иметь значение 0

NULL — это часть стандарта C/C++ а не ОС. Но у ОС написанных на С/C++ и ОС рассчитанных на исполнение кода написанного на C/C++ в подавляющем большинстве случаев действует указанная мною схема с резервированием тех адресов памяти куда вероятнее всего может быть разыменован NULL. Со вполне однозначным подтекстом резервирования той части адресного пространства где находится специальный адрес NULL и производные от него. Да, в embedded, там где нет, собственно, самой виртуальной памяти, такой защиты может не быть.

Адрес памяти* с значением 0 не то же самое что указатель с присвоением ему NULL.

И то и другое — интегральные величины одного типа, обе равные 0. Причем в случае NULL они даже свободно кастятся друг к другу. Для nullptr_t такое преобразование запрещено и вот как раз этот запрет на cast to integer для нулевого указателя — это синтаксический сахар.

Довольно абсурдно приводить вывод компилятора когда мы говорим о абстрактных понятиях стандарта языка.

Отнюдь не абсурдно в контексте вопроса «синтаксический сахар» это или отражение чего-то совершенно реального.
Факт в том что текст Стандарта определяет NULL как An integer constant expression with the value 0.

И то и другое — интегральные величины одного типа, обе равные 0.

Вы разницу между определением символа NULL и адресом указателя, которому присвоили NULL видите? В стандарте могло быть написано что NULL имеет значение 42, вы бы после этого так же спорили что теперь все указатели которым присвоено NULL имеют адрес 42?)


NULL — это часть стандарта C/C++ а не ОС. Но у ОС написанных на С/C++ и ОС рассчитанных на исполнение кода написанного на C/C++ в подавляющем большинстве случаев действует указанная мною схема с резервированием тех адресов памяти куда вероятнее всего может быть разыменован NULL. Со вполне однозначным подтекстом резервирования той части адресного пространства где находится специальный адрес NULL и производные от него. Да, в embedded, там где нет, собственно, самой виртуальной памяти, такой защиты может не быть.

Не очень понятно что вы этим доказываете и при чем тут ОС на C++ о которых стандарт ничего не говорит. У автора в тексте сказано как раз что в ОС указатель с адресом 0 "зарезервирован" и тут же идёт отсылка к nullptr, хотя связи между адресом 0 в ОС и значением nullptr стандарт не определяет. О чем в том числе и мой исходный комментарий.


Отнюдь не абсурдно в контексте вопроса «синтаксический сахар» это или отражение чего-то совершенно реального.

Грубо говоря если вам после выделения памяти будет выдан блок с 0 до…, то такой указатель равен NULL не будет, хоть и одно и другое вроде как ноль. Потому и мнение автора, на которое отвечал я, о том что указатель с NULL и nullptr являются одними из адресов указателей в Си/C++, а не особым состоянием указателя, является мягко говоря не верным.

Грубо говоря если вам после выделения памяти будет выдан блок с 0 до…, то такой указатель равен NULL не будет, хоть и одно и другое вроде как ноль

Стандарт языка C/C++ прямо гарантирует что такой блок Вам выделен не будет. Поэтому да, получить NULL путем выделения памяти невозможно.

В стандарте могло быть написано что NULL имеет значение 42

В Стандарте написано что NULL имеет значение 0. Точка. Причем выбор именно такой константы имеет глубокое значение, уходящее корнями в историю языка C и архитектуру CPU. Сравнение с нулем на старых машинах было намного более быстрой операцией чем сравнение с любым другим числом. Оно чисто аппаратно очень просто реализуется и на многих архитектурах CPU включая PDP-11 послужившую основой для разработки языка C сравнением выполняется автоматически на каждую операцию, выставляя «zero flag» немедленно доступный для branching. И в том же x86 эта оптимизация используется до сих пор. В силу чего NULL, собственно, и выбран 0, хотя чисто теоретически таки да, можно было бы взять и любое другое значение. Но 0 был удобнее и Стандарт гарантирует что NULL — это действительно 0.

хотя связи между адресом 0 в ОС и значением nullptr стандарт не определяет

Стандарт определяет что если рассматривать указатель как целое число то nullptr будет равен 0.
Стандарт языка C/C++ прямо гарантирует что такой блок Вам выделен не будет. Поэтому да, получить NULL путем выделения памяти невозможно.

Вы путаете концепт языка null pointer и физический адрес в памяти равный нулю, а так же мешаете стандарты двух разных языков вместе. Приведите, пожалуйста, цитату из стандарта языка Си где написано что язык гарантирует при выделении памяти что адрес в указателе не будет равным 0 (не путайте с null pointer). Думаю что такой цитаты вы не найдете.


В Стандарте написано что NULL имеет значение 0. Точка.

Вы продолжаете борьбу с ветряными мельницами. Заметьте, я не говорю что NULL имеет значение отличное от 0, я лишь обращаю ваше внимание на то, что это не так важно в контексте языка. Компилятор, даже по вашей приведенной цитате из стандарта, должен обеспечить всего лишь две вещи: определить макрос NULL как 0 и чтобы указатель, которому присвоили значение 0 или NULL, стал null pointer. Это означает только переход указателя в особое состояние с именем null pointer, но гарантий присваивания ему физического адреса памяти 0 не даёт. И это хорошо, потому как не ограничивает разработчиков компилятора в интерпретации значения null pointer. И тот факт что в вашей любимой или не очень ОС на популярном или не очень компиляторе в результате в указатель записывается значение 0 никак не влияет на то какnull pointer определяет стандарт.

Вы путаете концепт языка null pointer и физический адрес в памяти равный нулю

Стандарт языка указывает на то реализацией концепта языка null pointer что виртуальный адрес в памяти равный нулю

Приведите, пожалуйста, цитату из стандарта языка Си где написано что язык гарантирует при выделении памяти что адрес в указателе не будет равным 0

Он гарантирует что 1) NULL == (void*)0 и 2) void* maloc() как и любая другая операция по созданию объекта в случае успешного выполнения не вернет NULL. Сложите два и два.

Это означает только переход указателя в особое состояние с именем null pointer, но гарантий присваивания ему физического адреса памяти 0 не даёт

Во всех известных мне имплементациях — дает. Довольно сложно, скажу я Вам, реализовать что-то иное в силу наличия гарантий что занулив указатель (в том числе через memset (0) что широко используется в over 9000 распространенных программ и библиотек) Вы получите NULL. Еще один хорошо известный пример — это инициализация статических переменных. Стандарт гарантирует что соответствующая память будет zero-initialized. Угадайте чему равно начальное значение статического указателя.

Я не понимаю к чему Вы ведете этот спор, честно. Стандарт гарантирует что NULL — это 0 а nullptr — это тот же 0, но не-интегрального типа. Во всех реализациях NULL — это 0. Есть куча программ опирающихся на тот факт что NULL — это 0. В операционных системах с виртуальной памятью страница памяти содержащая адрес 0 как правило зарезервирована чтобы любой доступ к ней вызывал SEGFAULT. Да, чисто технически, можно было бы сделать NULL равным какой-то другой константе, не 0. Но это не соответствует ни Стандарту ни одной реальной реализации.
Он гарантирует что 1) NULL == (void)0 и 2) void maloc() как и любая другая операция по созданию объекта в случае успешного выполнения не вернет NULL. Сложите два и два.

И получите совсем не то что ожидаете. Вы ожидаете что функция malloc никогда не вернёт указатель с адресом 0, а на самом деле вам (согласно стандарту) эта функция никогда не вернёт null pointer. В результате своего довольно вольного трактования стандарта вы удивитесь, когда, к примеру, побитовое сравнение null pointer указателя и NULL (или 0) у вас неожиданно не сойдется, хотя вам никто этого и не гарантировал.


Я не понимаю к чему Вы ведете этот спор, честно.

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


over 9000 распространенных программ и библиотек

а не того что там реально написано, защищаете по сути ошибки в статье.


Стандарт что Си, что C++ не опираются на реализации, а все как раз наоборот. Комитет конечно старается угодить всем, в том числе и разработкам компилятора, но аргументы в стиле "да все так делают" тут не работают.
Вы можете продолжать напирать на то что в конкретных реализация каких-то компиляторов на каких-то платформах ваша вольная трактовка стандарта подтверждается, но, честно говоря, этим только вводите в заблуждение тех любопытных, кто доберется до конца этой ветки обсуждения.
Для них оставлю неплохое объяснение того что реально генерирует стандарт C по поводу NULL: https://stackoverflow.com/a/1296865/827263

Вы можете продолжать напирать на то что в конкретных реализация каких-то компиляторов на каких-то платформах ваша вольная трактовка стандарта подтверждается.

Практика — критерий истины, уж извините. Вы пока не привели ни одного (sic!) контрпримера. Да, номинально Стандарт разделяет целое число 0 и NULL pointer, но он это делает не потому что там может быть какая-то другая константа, а потому что указатель может плохо приводиться к целочисленному типу вообще. Например Стандарт допускает что у указателя может быть более одного челочисленного представления что делает саму постановку вопроса о «побитовом сравнении» на подобных платформах бессмысленной. Или, к примеру, два одинаковых (побитово) указателя на разные типы могут адресовать разные области памяти. Но на платформах без подобных, хм, скажем мягко, нетривиальных особенностей адресации памяти, там где можно провести однозначное соответствие между целыми числами и адресами в памяти, NULL будет ассоциирован с 0. И если Вы не работаете с машинами с экзотической адресацией памяти (а я таких давным-давно что-то не наблюдаю) то следует исходить именно из этого, а не из абстрактной теории.
Практика — критерий истины, уж извините. Вы пока не привели ни одного (sic!) контрпримера.

Не извиню. Со стандартами это так не работает.


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

То есть в обсуждении стандарта языка (по сути теории) вы предлагаете из этой самой теории не исходить. Однако.

Статья в целом малость капитанская, много всего упомянуто и ничего толком не разобрано :). Плюс в целом определенная каша, когда в контексте C++ упоминается malloc и не упоминается new.

Из интересных вещей по менеджменту памяти в плюсах — советую более-менее сведущим в теме читателям почитать про C++17 PMR. Отличная штука, мне ее сильно не хватало. Есть еще обалденная книжка «Mastering the C++17 STL», там очень хорошо описаны вопросы использования allocator — тоже рекомендую.

Новичкам — советую освоить «умные указатели» и забыть про ручное управление памятью, в плюсах оно в 99.9% случаев не нужно (и я это утверждаю как разработчик нагруженных soft realtime систем).
Sign up to leave a comment.

Articles