А при чём здесь volatile? В описании __sync_val_compare_and_swap про него ничего не сказано -- ни в доках gcc, ни в доках Intel Itanium Processor-specific Application Binary Interface, откуда он родом. И если убрать в вашем коде volatile, ассемблерный код не изменится. В том числе при сборке с оптимизацией (-O2).
volatile у переменной означает, что чтение и запись этой переменной --- непосредственные элементы наблюдаемого поведения (разновидность ввода и вывода программы, соответственно, а не операции над ячейками абстрактной памяти C/C++). Невозможность тех или иных преобразований кода компилятором --- это уже следствие.
Оптимизации не могут сломать корректный код. В свою очередь, запрет оптимизаций не может сделать некорректный код корректным. Если volatile используется для запрета оптимизаций, то он используется не по назначению. Использование volatile вместо атомиков --- яркий тому пример.
Из драфта стандарта C++11, 1.10 Multi-threaded executions and data races [intro.multithread], абзац 21:
The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.
Как видите, никаких поблажек volatile-у не сделано.
Да-да, конечно, ведь лучше запрятаться за высокоуровневыми абстракциями, чтобы потом получать приложения, тормозящие даже на дорогущем оборудовании
atomic-и (особенно с явным указанием memory_order, особенно если это memory_order_relaxed) --- ни разу не "высокоуровневая абстракция".
Вопрос терминологии, конечно, но под "массой" действительно лучше всегда понимать инвариантную, которая от скорости тела/выбора ИСО не зависит. "Релятивистских" масс можно выделить как минимум две ("продольную" и "поперечную"; для произвольного угла вообще тензор получается) и они только мути наводят, неуклюже маскируя уравнения СТО под уравнения классической механики.
Ну вот компилятор раста и говорит, что так делать не надо)
Не говорит. Нет ничего плохого в наполнении пустой (или даже потенциально пустой, с выливанием старого чая, если нужно) чашки новым чаем — в том числе с точки зрения компилятора Rust.
Ой, адепт просто погулил private и кинул первую ссылку в гугле, но опозрился. Ой, а потом просто забил на ответ сославшись на то, что это враньё?
Ой, простите, при составлении списка наглой лжи я действительно совсем забыл про этот пункт.
Итак, вы писали:
Это костыли из модулей, о которых я ничего не говорил. Я говорил про права доступа для полей.
и, ещё раньше:
Кто угодно имеет полный доступ к объекту, как это было всегда в си.
Отвечаю: вот наиболее релевантный фрагмент моей «первой ссылки из гугла», опровергающий ваш тезис про всеобщий доступ к объекту за счёт приватного права доступа для поля:
// Declare a public struct with a private field
pub struct Bar {
field: i32,
}
Никаких методов нет — есть просто отдельные функции.
Есть только drop, который не деструктор. Это что-то типа finalize в жава. Раст это изначально скриптуха с ГЦ
Все типы ссылочные, переменные не являются стореджем как в С/С++.
Но в основном они нужны потому, что в данной скриптухи нет перегрузки. В жаве так же, поэтому там так же используются статический методы для не-стандартных инициализаций.
Методы (не просто отдельные функции) есть.
Деструкторы есть. drop — это именно что детерминированно вызываемый деструктор, после которого столь же детерминированно вызываются деструкторы полей, а никак не «что-то типа finalize в жава».
ГЦ нет. (Давным-давно был, удалили в 2014-м, ещё до выхода первой стабильной версии Rust.) И никаким боком раст не подходит под определение «скриптухи» (ни по достоинствам, ни по недостаткам).
Типы НЕ ссылочные: переменные являются стореджем, как в C++.
В жаве перегрузка есть, в том числе для конструкторов, статические методы для создания объектов там используются по совсем другим причинам (например, для возможности создания объекта производного класса — погуглите «фабричный метод»).
Я ответил только на совсем наглую ложь с вашей стороны (чтобы другие читатели не поверили в неё на слово, а таки затратили пару секунд на гугление). Остальное оставлю без внимания, так как у высказываний настолько недобросовестных собеседников вес всё равно равен нулю.
Не надо фантазировать. В расте полноценные пользовательские типы данных с инкапсуляцией состояния, методами и деструкторами. Единственное, что их роднит с «голыми структурами (читай си)» — тривиальная перемещаемость (от которой, если очень нужно, можно уйти, спрятав объект за Pin-ом). И слово struct, которым (наряду с enum) они объявляются.
Т.е. нет никакие конструкторов, нету наследования, прав доступа, инициализаторов и прочего.
Наследования нет, но есть реализация интерфейсов (trait-ов) и композиция, которые решают те же задачи более изящно.
Конструкторов нет, поскольку в отсутствие традиционного наследования задача инициализации объекта корректнее и гибче решается функцией (без параметра self), возвращающей объект. В такой функции можно вычислить значения всех полей до создания объекта, что полностью исключает саму фазу инициализации (в течение которой объект находится в нецелостном состоянии, но виден конструктором и вызываемыми из него методами), а также из неё можно вернуть нечто более сложное, чем просто инициализированный объект (например, монаду вроде Result или Option).
Всякий раз перед возвратом (CO_YIELD) из «корутины» мы запоминаем место (номер строки), откуда осуществляем возврат. Когда функция вызывается вновь, мы возвращаемся в это место (сразу после оператора return).
Достигается это путём обёртывания всего тела функции в switch и расстановке меток case __LINE__: во всех местах возврата, благо switch в C/C++ позволяет прыгать даже внутрь вложенных блоков (самое известное применение чему — Устройство Даффа).
А в C null pointer обязательно имеет именно нулевое битовое представление?
Нет, конечно. Я имею в виду, что использование memset для зануления всех полей структуры (равно как и calloc для выделения памяти под структуру с изначальным её занулением) — распространённый приём в Си, поэтому, если последовательность из нулевых байтов не будет хотя бы одним из возможных object representation для null pointer value, то сломается куча кода.
А из соображений совместимости Си и C++ у этих языков должны быть одинаковые object representation для null pointer value.
Вас просили взять «адрес существующей функции». Функция у вас здесь одна — это main, и адрес вы её не берёте (да и нельзя по Стандарту). Вы вообще никакой адрес здесь не берёте, а просто сравниваете value-initialized переменную типа «указатель на функцию» с null pointer value.
Во-первых, я здесь приводил прямую цитату из Стандарта C++. Не может.
Во-вторых, по поводу ваших ссылок. Если объявить переменную типа «указатель на функцию», то она (а не адрес какой-либо функции!), конечно, может иметь значение null pointer value.
Только вот в этом случае она ни на какую функцию не указывает и потому вызывать её нельзя. Более того, последнее прямым текстом сказано по одной из ваших же ссылок:
>… calling a null pointer is undefined
Представим на секунду, что у некой функции нулевой адрес (в смысле, её адрес — null pointer value). Возьмём адрес этой функции. Попробуем её вызвать через получившийся указатель… И получим неопределённое поведение. Не находите это странным? Ваша ссылка не подтверждает, а опровергает ваш тезис.
В описанной на reddit-е ситуации в программе неопределённое поведение. Неопределённое поведение может проявляться _как_угодно_, это в принципе ничего не может говорить о языке.
В данном случае компилятор видит, что указатель Do может иметь лишь два возможных значения: _либо_ null pointer value, _либо_ адрес функции EraseAll. Вызов null pointer value — неопределённое поведение, поэтому компилятор, видя вызов через указатель Do, предполагает (имеет полное право, по самому определению UB), что значением указателя является всё-таки адрес функции EraseAll — и в качестве оптимизацию производит замену вызова по указателю на вызов непосредственно EraseAll — при том, что на самом деле указатель Do всегда имеет значение null pointer value.
Это не говорит о том, что адрес функции EraseAll есть null pointer value. Это просто пример того, как компилятор честно пришёл к _ложному_ выводу по той причине, что программист нарушил контракт со своей стороны (допустил неопределённое поведение — в данном случае вызов через нулевой указатель).
Потому что operator<< перегружен для const void* (к которому приводятся любые объектные типы), но не для указателей на функции (кроме функций с сигнатурами манипуляторов вроде endl или flush). Выводить адреса объектов полезнее, чем адреса функций.
А при чём здесь volatile? В описании __sync_val_compare_and_swap про него ничего не сказано -- ни в доках gcc, ни в доках Intel Itanium Processor-specific Application Binary Interface, откуда он родом. И если убрать в вашем коде volatile, ассемблерный код не изменится. В том числе при сборке с оптимизацией (-O2).
volatile у переменной означает, что чтение и запись этой переменной --- непосредственные элементы наблюдаемого поведения (разновидность ввода и вывода программы, соответственно, а не операции над ячейками абстрактной памяти C/C++). Невозможность тех или иных преобразований кода компилятором --- это уже следствие.
Оптимизации не могут сломать корректный код. В свою очередь, запрет оптимизаций не может сделать некорректный код корректным. Если volatile используется для запрета оптимизаций, то он используется не по назначению. Использование volatile вместо атомиков --- яркий тому пример.
Из драфта стандарта C++11, 1.10 Multi-threaded executions and data races [intro.multithread], абзац 21:
Как видите, никаких поблажек volatile-у не сделано.
atomic-и (особенно с явным указанием memory_order, особенно если это memory_order_relaxed) --- ни разу не "высокоуровневая абстракция".
Как тут на классику не сослаться)
http://www.mathnet.ru/links/2945dcbfee3ec4e1da58416946443647/ufn7675.pdf
Вопрос терминологии, конечно, но под "массой" действительно лучше всегда понимать инвариантную, которая от скорости тела/выбора ИСО не зависит. "Релятивистских" масс можно выделить как минимум две ("продольную" и "поперечную"; для произвольного угла вообще тензор получается) и они только мути наводят, неуклюже маскируя уравнения СТО под уравнения классической механики.
Ну а «пить из пустой чашки» действительно никому не нужно. А если всё же нужно, используют Option или std::mem::take.
После чего её, конечно, можно будет и справа использовать.
Итак, вы писали: и, ещё раньше: Отвечаю: вот наиболее релевантный фрагмент моей «первой ссылки из гугла», опровергающий ваш тезис про всеобщий доступ к объекту за счёт приватного права доступа для поля:
Занавес.
Методы (не просто отдельные функции) есть.
Деструкторы есть. drop — это именно что детерминированно вызываемый деструктор, после которого столь же детерминированно вызываются деструкторы полей, а никак не «что-то типа finalize в жава».
ГЦ нет. (Давным-давно был, удалили в 2014-м, ещё до выхода первой стабильной версии Rust.) И никаким боком раст не подходит под определение «скриптухи» (ни по достоинствам, ни по недостаткам).
Типы НЕ ссылочные: переменные являются стореджем, как в C++.
В жаве перегрузка есть, в том числе для конструкторов, статические методы для создания объектов там используются по совсем другим причинам (например, для возможности создания объекта производного класса — погуглите «фабричный метод»).
Я ответил только на совсем наглую ложь с вашей стороны (чтобы другие читатели не поверили в неё на слово, а таки затратили пару секунд на гугление). Остальное оставлю без внимания, так как у высказываний настолько недобросовестных собеседников вес всё равно равен нулю.
Права доступа есть. doc.rust-lang.org/reference/visibility-and-privacy.html
Наследования нет, но есть реализация интерфейсов (trait-ов) и композиция, которые решают те же задачи более изящно.
Конструкторов нет, поскольку в отсутствие традиционного наследования задача инициализации объекта корректнее и гибче решается функцией (без параметра self), возвращающей объект. В такой функции можно вычислить значения всех полей до создания объекта, что полностью исключает саму фазу инициализации (в течение которой объект находится в нецелостном состоянии, но виден конструктором и вызываемыми из него методами), а также из неё можно вернуть нечто более сложное, чем просто инициализированный объект (например, монаду вроде Result или Option).
Правильно (если тип беззнаковый, каким он в любом случае должен быть):
Достигается это путём обёртывания всего тела функции в switch и расстановке меток case __LINE__: во всех местах возврата, благо switch в C/C++ позволяет прыгать даже внутрь вложенных блоков (самое известное применение чему — Устройство Даффа).
вычисляемый gotoswitch:Нет, конечно. Я имею в виду, что использование memset для зануления всех полей структуры (равно как и calloc для выделения памяти под структуру с изначальным её занулением) — распространённый приём в Си, поэтому, если последовательность из нулевых байтов не будет хотя бы одним из возможных object representation для null pointer value, то сломается куча кода.
А из соображений совместимости Си и C++ у этих языков должны быть одинаковые object representation для null pointer value.
Что-то уж совсем толсто.
Во-вторых, по поводу ваших ссылок. Если объявить переменную типа «указатель на функцию», то она (а не адрес какой-либо функции!), конечно, может иметь значение null pointer value.
Только вот в этом случае она ни на какую функцию не указывает и потому вызывать её нельзя. Более того, последнее прямым текстом сказано по одной из ваших же ссылок:
>… calling a null pointer is undefined
Представим на секунду, что у некой функции нулевой адрес (в смысле, её адрес — null pointer value). Возьмём адрес этой функции. Попробуем её вызвать через получившийся указатель… И получим неопределённое поведение. Не находите это странным? Ваша ссылка не подтверждает, а опровергает ваш тезис.
В описанной на reddit-е ситуации в программе неопределённое поведение. Неопределённое поведение может проявляться _как_угодно_, это в принципе ничего не может говорить о языке.
В данном случае компилятор видит, что указатель Do может иметь лишь два возможных значения: _либо_ null pointer value, _либо_ адрес функции EraseAll. Вызов null pointer value — неопределённое поведение, поэтому компилятор, видя вызов через указатель Do, предполагает (имеет полное право, по самому определению UB), что значением указателя является всё-таки адрес функции EraseAll — и в качестве оптимизацию производит замену вызова по указателю на вызов непосредственно EraseAll — при том, что на самом деле указатель Do всегда имеет значение null pointer value.
Это не говорит о том, что адрес функции EraseAll есть null pointer value. Это просто пример того, как компилятор честно пришёл к _ложному_ выводу по той причине, что программист нарушил контракт со своей стороны (допустил неопределённое поведение — в данном случае вызов через нулевой указатель).