Как стать автором
Обновить
4
0
Дмитрий Алексеев @deviant_9

Пользователь

Отправить сообщение

А при чём здесь 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) --- ни разу не "высокоуровневая абстракция".

Как тут на классику не сослаться)
http://www.mathnet.ru/links/2945dcbfee3ec4e1da58416946443647/ufn7675.pdf

Вопрос терминологии, конечно, но под "массой" действительно лучше всегда понимать инвариантную, которая от скорости тела/выбора ИСО не зависит. "Релятивистских" масс можно выделить как минимум две ("продольную" и "поперечную"; для произвольного угла вообще тензор получается) и они только мути наводят, неуклюже маскируя уравнения СТО под уравнения классической механики.

Точнее, просто изначальная аналогия неудачная (не иллюстрирует разницу реализации перемещения в Rust и C++). «Наливать новый чай» позволяют оба языка.

Ну а «пить из пустой чашки» действительно никому не нужно. А если всё же нужно, используют Option или std::mem::take.
Именно что с левой. Речь шла не о том, чтобы пить из пустой чашки, а о том, чтобы
налить в нее новый чай

После чего её, конечно, можно будет и справа использовать.
Ну вот компилятор раста и говорит, что так делать не надо)
Не говорит. Нет ничего плохого в наполнении пустой (или даже потенциально пустой, с выливанием старого чая, если нужно) чашки новым чаем — в том числе с точки зрения компилятора Rust.
Ой, адепт просто погулил private и кинул первую ссылку в гугле, но опозрился. Ой, а потом просто забил на ответ сославшись на то, что это враньё?
Ой, простите, при составлении списка наглой лжи я действительно совсем забыл про этот пункт.
Итак, вы писали:
Это костыли из модулей, о которых я ничего не говорил. Я говорил про права доступа для полей.
и, ещё раньше:
Кто угодно имеет полный доступ к объекту, как это было всегда в си.
Отвечаю: вот наиболее релевантный фрагмент моей «первой ссылки из гугла», опровергающий ваш тезис про всеобщий доступ к объекту за счёт приватного права доступа для поля:
// Declare a public struct with a private field
pub struct Bar {
    field: i32,
}

Все типы ссылочные
про ссылочные типы я ничего не говорил.
Занавес.
Никаких методов и деструкторов нет.
Никаких методов нет — есть просто отдельные функции.
Есть только drop, который не деструктор. Это что-то типа finalize в жава. Раст это изначально скриптуха с ГЦ
Все типы ссылочные, переменные не являются стореджем как в С/С++.
Но в основном они нужны потому, что в данной скриптухи нет перегрузки. В жаве так же, поэтому там так же используются статический методы для не-стандартных инициализаций.

Методы (не просто отдельные функции) есть.

Деструкторы есть. drop — это именно что детерминированно вызываемый деструктор, после которого столь же детерминированно вызываются деструкторы полей, а никак не «что-то типа finalize в жава».

ГЦ нет. (Давным-давно был, удалили в 2014-м, ещё до выхода первой стабильной версии Rust.) И никаким боком раст не подходит под определение «скриптухи» (ни по достоинствам, ни по недостаткам).

Типы НЕ ссылочные: переменные являются стореджем, как в C++.

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

Я ответил только на совсем наглую ложь с вашей стороны (чтобы другие читатели не поверили в неё на слово, а таки затратили пару секунд на гугление). Остальное оставлю без внимания, так как у высказываний настолько недобросовестных собеседников вес всё равно равен нулю.
там голые структуры взятые из llvm(читай си).
Не надо фантазировать. В расте полноценные пользовательские типы данных с инкапсуляцией состояния, методами и деструкторами. Единственное, что их роднит с «голыми структурами (читай си)» — тривиальная перемещаемость (от которой, если очень нужно, можно уйти, спрятав объект за Pin-ом). И слово struct, которым (наряду с enum) они объявляются.
Т.е. нет никакие конструкторов, нету наследования, прав доступа, инициализаторов и прочего.
Права доступа есть. doc.rust-lang.org/reference/visibility-and-privacy.html

Наследования нет, но есть реализация интерфейсов (trait-ов) и композиция, которые решают те же задачи более изящно.

Конструкторов нет, поскольку в отсутствие традиционного наследования задача инициализации объекта корректнее и гибче решается функцией (без параметра self), возвращающей объект. В такой функции можно вычислить значения всех полей до создания объекта, что полностью исключает саму фазу инициализации (в течение которой объект находится в нецелостном состоянии, но виден конструктором и вызываемыми из него методами), а также из неё можно вернуть нечто более сложное, чем просто инициализированный объект (например, монаду вроде Result или Option).
constexpr auto shiftSize = sizeof(std::underlying_type_t<Flags>) * 8 - 1;

  • Не 8, а CHAR_BIT.
  • Всё равно будет неправильно, поскольку у целочисленного типа могут быть биты-заполнители (padding bits), которые здесь тоже подсчитаются.

Правильно (если тип беззнаковый, каким он в любом случае должен быть):
#include <limits>
constexpr auto shiftSize = std::numeric_limits<std::underlying_type_t<Flags>>::digits - 1;
Совершенно аналогичный код можно написать на чистом Си.
Всякий раз перед возвратом (CO_YIELD) из «корутины» мы запоминаем место (номер строки), откуда осуществляем возврат. Когда функция вызывается вновь, мы возвращаемся в это место (сразу после оператора return).
Достигается это путём обёртывания всего тела функции в switch и расстановке меток case __LINE__: во всех местах возврата, благо switch в C/C++ позволяет прыгать даже внутрь вложенных блоков (самое известное применение чему — Устройство Даффа).
(это предварительный синтаксис, потому что в стандарте C++ корутин нет):
Зато есть старый добрый вычисляемый goto switch:
#include <iostream>
#include <tuple>

#define CO_BEGIN \
	{ \
		switch (lineno) \
		{ \
		case 0: \
	//
#define CO_END }}
#define CO_YIELD(value) \
	do { \
		lineno = __LINE__; \
		return (value); \
		case __LINE__:; \
	} while (false) \
	//

class PytripleGenerator
{
public:
	std::tuple<int, int, int> next()
	CO_BEGIN
		for (z = 1; ; ++z)
			for (x = 1; x <= z; ++x)
				for (y = x; y <= z; ++y)
					if (x*x + y*y == z*z)
						CO_YIELD(std::make_tuple(x, y, z));
	CO_END
private:
	int lineno = 0;
	int x = 1, y = 1, z = 1;
};

PytripleGenerator pytriples()
{
	return PytripleGenerator();
}

int main()
{
	PytripleGenerator py;
	for (int i = 0; i < 100; ++i)
	{
		auto [x, y, z] = py.next();
		std::cout << "(" << x << ", " << y << ", " << z << ")\n";
	}
}
А в C null pointer обязательно имеет именно нулевое битовое представление?


Нет, конечно. Я имею в виду, что использование memset для зануления всех полей структуры (равно как и calloc для выделения памяти под структуру с изначальным её занулением) — распространённый приём в Си, поэтому, если последовательность из нулевых байтов не будет хотя бы одним из возможных object representation для null pointer value, то сломается куча кода.

А из соображений совместимости Си и C++ у этих языков должны быть одинаковые object representation для null pointer value.
Пардон — не value-initialized, а zero-initialized. Но для переменной типа «указатель на функцию» это одно и то же.
Вас просили взять «адрес существующей функции». Функция у вас здесь одна — это 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). Выводить адреса объектов полезнее, чем адреса функций.
Если там есть поле mutable std::vector<double> с миллиардом элементов, то самое оно.
1
23 ...

Информация

В рейтинге
Не участвует
Откуда
Москва, Москва и Московская обл., Россия
Дата рождения
Зарегистрирован
Активность