Скорее, выполнять преобразования при решении уравнения.
Ошибки бывают, и достаточно часто. Это — данность, с которой приходится иметь дело.
Однако, с этой данностью можно эффективно бороться, снижая количество ошибок до уровня, близкого к 0.
Для этого необходимо перепроверять способом, отличным от прямого вычисления.
Примерно, как проверяют найденные корни уравнения подстановкой их значений в уравнение и проверкой, что знак равенства выполняется.
В этом случае работает перемножение довольно малых вероятностей. Если вероятность ошибиться как при прямом вычислении, так и при перепроверке — 10%, то общая вероятность после их перемножения даёт всего 1%, что явно лучше 10%.
Если исходные вероятности ещё меньше, то эффект — ещё значительнее.
Можно и вторую перепроверку использовать каким-нибудь третьим способом, чтобы ещё уменьшить результирующую вероятность ошибиться, но обычно достаточно одной перепроверки.
А совсем без перепроверки ошибки обычно слишком часты.
Any pointer to object can be cast to any other pointer to object. If the value is not correctly aligned for the target type, the behavior is undefined. Otherwise, if the value is converted back to the original type, it compares equal to the original value. If a pointer to object is cast to pointer to any character type, the result points at the lowest byte of the object and may be incremented up to sizeof the target type (in other words, can be used to examine object representation or to make a copy via memcpy or memmove).
Непонятно насчёт разыменования, но, поскольку для pointer to any character type явно подразумевается разыменование, хотя явным образом упорно об этом умалчивается, то и для преобразованного pointer to object логично ожидать разрешения на разыменование, лишь бы выравнивание было корректным.
Отличия малы потому что они смотрят котировки друг у друга, бывает что и в автоматическом режиме. И ставят такие же или чуть лучше чтобы переманить клиентов.
Соответственно, аргумент:
а нечто синтезированное по хитрому алгоритму. Цель которого в том чтобы брокер выиграл в 80% сделок (или 99 если брокер жадный).
Ну, это натянутое утверждение. Для типов "массив" любой вложенности можно создавать алиасы через тот же using, существует тип "указатель на массив", decltype тоже прекрасно в курсе, что там за массив в конкретном случае и какие размерности он имеет. У компилятора нет никаких проблем отличить один массив от другого, коль скоро у него есть доступ к соответствующей декларации.
Верно.
Одновременно с этим у массива, в отличие от структуры, нет своей const/volatile-ности, значение массива, в отличие от значения структуры, нельзя присвоить значению однотипного массива, а также нельзя инициализировать.
Как следствие, в отличие от структуры, его нельзя по значению ни передать в функцию, ни вернуть оттуда.
При этом, что интересно, в составе структуры он прекрасно может быть использован во всех этих качествах, но — только от имени структуры.
Именно поэтому массив — это не "нетип", а именно "недотип".
Вы слишком много внимания уделяете конкретной гипотетической реализации.
Это — для того, чтобы иметь возможность дать ответ в общем случае, а не гадать.
Я же воспринимаю стандарты как описание некоей виртуальной машины, в котором написано, что эта виртуальная машина гарантированно может/должна прожевать (с тем или иным результатом), и на что не дается никаких гарантий.
Без описания того, как всё устроено, нельзя дать ответ в общем случае.
имея лишь указатель на первый элемент первого массива, нет легального способа получить доступ к элементам остальных массивов (требование "every byte that would be reachable through the result is reachable through p" не выполняется).
Хорошо, я изменю оригинальный пример с cppreference, создав массив не из 2-х подмассивов, а из одного:
int x2[1][10];
auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0]));
Теперь, по логике того объяснения, которое было в оригинальном примере, здесь UB нет, верно?
std::launder применим только если по "отмываемому" адресу действительно уже существует объект типа T. Об этом недвусмысленно написано в его описании по ссылке. Нельзя пытаться "отмывать" многомерный массив в одномерный, это не будет работать, потому что по соответствующему адресу находится многомерный массив, а не одномерный, и std::launder для этого не предназначен.
В C/C++ нет многомерных массивов, зато есть массивы массивов.
И сам тип массив является "недотипом": один однотипный массив нельзя присвоить другому, в отличие от тех же структур, также массив нельзя инициализировать однотипным массивом.
В этом смысле объектов типа "массив" не существует. Косвенно это можно обнаружить по тому, что квалификаторы const/volatile к самому массиву не применимы, только к типам самих элементов.
Опять же, — в отличие от тех же структур.
В чём же может быть отличие между "одномерным" и "многомерным" массивом, учитывая вышесказанное, а также, учитывая, что элементы лежат "плотно"?
В стандарте очень часто — головоломка, потому что не описывается, что лежит в основе, но, кажется, я начинаю догадываться, что они могли иметь ввиду, хотя это также может быть неполным пониманием.
int arr[2][4]{{1, 2, 3, 4}, {5, 6, 7, 8}};
int *p0{arr[0]};
int *p1{arr[1]};
int res{0};
for (int i = 0; i < 5; ++i) {
if (p0 == p1) {
p0 = p1;
}
res += *p0++;
}
Если считать, что должны поддерживаться архитектуры, в которых доступ к памяти защищён по типу сегментов в IA-32 и принять, что при инициализации p0 выше "выдаётся" указатель, который безопасно можно разыменовывать только как int'ы, и — в пределах 4 int'ов в памяти, считая от начала памяти, занимаемой arr[0], и двигать, но не разыменовывать на 5-ый, иначе возникнет исключение общей защиты памяти, то дальше получается следующее.
При инициализации p1 выдаётся аналогичный указатель, но начало "сегмента" находится по адресу arr[1].
Тогда сравнение p0 == p1, в котором сравнивается только адресная составляющая, "срабатывает" как раз в тот момент, когда p0 ещё может иметь такое значение, но разыменовывать его уже нельзя.
При присваивании в теле if'а в p0 записывается полная информация из p1: не только информация об адресе, которая в этот момент совпадает, но и информация о том, какой сегмент памяти доступен без срабатывания общей защиты памяти.
И поэтому при наличии этого if'а дальнейшие итерации в цикле не приводят к срабатыванию этой защиты, то есть, UB, а без него однозначно привели бы.
Если продолжать рассуждать в этом смысле, то следующий код, по идее:
int (*ar)[8]{std::start_lifetime_as_array<int[8]>(&arr)};
должен создать указатель с адресом, равным адресу, по которому лежит исходный массив arr, доступом к объектам типа int и защитой в виде "сегмента", настроенного на все 8 элементов исходного массива.
И тогда проблем с одномерным доступом к всему массиву не возникает.
Но они не говорят, как всё устроено, и что за всеми этими штуками стои́т.
Там написано не про это.
Про это там тоже написано.
Там написано, что x2[1] недоступно через &x2[0][0], а через приведенное сочетание reinterpret_cast и std::launder кто-то пытается притвориться, что доступно - ведь через int(*)[10] оно действительно должно быть доступно, но фактически в данном случае это не так.
То есть, они неявно полагают, что можно адресную арифметику здесь применить, и на этом основании утверждать, что доступно?
Если снять одно измерение и повторить рассуждения, то получается следующее:
int x2[2];
auto p2 = std::launder(reinterpret_cast<int *>(&x2[0]));
// Undefined behavior: x2[1] would be reachable through the resulting pointer to x2[0]
// but is not reachable from the source
Или так — можно:
int x2[2];
int *p2 = &x2[0];
А так — UB?
int x2[2];
auto p2 = std::launder(reinterpret_cast<int *>(&x2[0]));
Я пытался ознакомиться с самой сутью, но мне это не удаётся сделать.
int x2[2][10];
auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0]));
// Undefined behavior: x2[1] would be reachable through the resulting pointer to x2[0]
// but is not reachable from the source
Именно этот пример вызвал наибольшие затруднения.
Как x2[1] может быть доступен через указатель на массив из 10 элементов?
А еще это как бы намекает что одних трюков с реинтерпрет кастами и лаундерингом недостаточно.
Скорее, это намекает на то, что сначала допустили какую-то ошибку, а теперь лихорадочно пытаются костылить, чтобы эту ошибку обойти.
Не говоря уж о том, что start_lifetime_as предназначены скорее немного для другого: например, есть у вас память, замапленная через mmap из файла какого-нибудь, и вы хотите превратить ее в объект (или массив объектов), чтобы потом с ней структурированно работать.
Предназначены они, по задумке, могут быть и для другого. Важно, для чего они могут быть использованы в принципе.
В частности, если они годятся для решения обсуждаемого вопроса, то не имеет значения, для чего они были задуманы изначально.
If the member used to access the contents of a union is not the same as the member last used to store a value, the object representation of the value that was stored is reinterpreted as an object representation of the new type (this is known as type punning).
У reinterpret_cast есть правила насчет того, когда получившийся указатель может быть разыменован, а когда - нет (и эти правила эквивалентны правилам для указателя, получившегося в результате каста одного указателя в другой через void*, потому что это абсолютно эквивалентные операции).
Это верно в отсутствие std::launder.
ИМХО все эти трюки просто-напросто опасны, не нужны и не стоят потраченного времени.
Думаю, что конкретно этот "трюк" ограничен избыточно. Не уследили за компиляторописателями.
Хотя, — вот вариант, который обещает быть рабочим:
#include <memory>
constexpr int foo()
{
int arr[2][4]{{1, 2, 3, 4}, {5, 6, 7, 8}};
int (&ar)[8]{*std::start_lifetime_as_array<int[8]>(&arr)};
// int *ar{std::start_lifetime_as_array<int>(&arr, 8)};
int res{0};
for (int i = 0; i < 5; ++i) {
res += ar[i];
}
return res;
}
static_assert(foo());
int main()
{}
Говорят, что пока ещё ни один из компиляторов не поддерживает ни start_lifetime_as, ни start_lifetime_as_array.
Однако, само название намекает, что данные "трюки" настолько востребованы, что для них даже добавили специальные функции.
Для оптимизаций. Видите, как здесь gcc мощно соптимизировал?
Всплывающее описание для инструкции процессора ud2:
"Generates an invalid opcode exception. This instruction is provided for software testing to explicitly generate an invalid opcode exception. The opcodes for this instruction are reserved for this purpose."
Правда, я боюсь, что теперь вы будете ругаться ещё сильнее.
Ну, constexpr — это особая ипостась, преобразовать не даёт.
Через reinterpret_cast — потому что становится не constexpr, а через двойной static_cast через void * — сначала потому что "cast from 'void *' is not allowed in a constant expression in C++ standards before C++2c", а потом, после включения C++2c, — потому что "cast from 'void *' is not allowed in a constant expression because the pointed object type 'int[2][4]' is not similar to the target type 'int[8]'".
Наверное, следует подождать, когда reinterpret_cast станет доступен в constexpr-функциях.
constexpr int foo()
{
int arr[2][4]{{1, 2, 3, 4}, {5, 6, 7, 8}};
int *p0{arr[0]};
int res{0};
for (int i = 0; i < 5; ++i) {
res += *p0++;
}
return res;
}
static_assert(foo());
int main()
{}
Получается "read of dereferenced one-past-the-end pointer is not allowed in a constant expression".
constexpr int foo()
{
int arr[2][4]{{1, 2, 3, 4}, {5, 6, 7, 8}};
int *p0{arr[0]};
int *p1{arr[1]};
int res{0};
for (int i = 0; i < 5; ++i) {
if (p0 == p1) {
p0 = p1;
}
res += *p0++;
}
return res;
}
static_assert(foo());
int main()
{}
Этот if - бессмысленный, потому что выполнение его тела ничего не меняет:
if (p0 == p1) {
p0 = p1;
}
Однако, без него наблюдается "one-past-the-end pointer is not allowed".
Здесь у меня возникло какое-то ощущение, что компиляторостроители перегнули палку в этом месте, борясь за пространство для оптимизаций.
Мне не удалось найти способа для constexpr-функций получить чистое решение для линейного доступа ко всему объекту.
Не уверен, требуется ли здесь "отмывание" с помощью std::launder, ибо эта функция как будто специально описана так непонятно, что без тайного знания понять невозможно.
Для C, возможно, тип тоже должен приводиться (если разыменование и сразу же взятие адреса &x[0] для C не "отвязывает" указатель от подобъекта в том смысле, что "отвязанным" указателем можно "гулять" по всему объекту):
printf(" %d", (*(int(*)[8])&a)[i];
Но, в любом случае, это не должно быть явное a[0][i], где нарушение границ налицо.
Результатом первого оператора индексирования a[0] будет указатель на int[4]
Конечно же, нет. Тип выражения a[0] отличается от указателя на int[4].
printf(" %d", a[0][i]);
Здесь двумерный массив a используется как одномерный: в цикле мы обращаемся к элементам через a[0][i], где i пробегает от 0 до ROWS * COLS. А что тут неправильного?
Неправильным здесь является способ использования многомерного массива как одномерного.
Ведь согласно стандарту, все элементы массива располагаются в памяти последовательно
Именно поэтому следует получить адрес первого элемента и проиндексировать его:
printf(" %d", i[&**a]);
Если этот код непонятен, можно попроще:
printf(" %d", (&**a)[i]);
Если и этот непонятен, можно ещё проще:
printf(" %d", (&a[0][0])[i]);
В последнем варианте явным образом видно, что сначала был взят адрес первого элемента, который был тут же проиндексирован — ровно то, о чём я написал перед первым примером.
Ни один компилятор — не то, что не ругался на UB, но даже и не пикнул: все компиляторы сгенерировали правильный код, который отработал и напечатал ожидаемую последовательность.
Это — результат правильного способа использования многомерного массива как одномерного.
Скорее, выполнять преобразования при решении уравнения.
Ошибки бывают, и достаточно часто.
Это — данность, с которой приходится иметь дело.
Однако, с этой данностью можно эффективно бороться, снижая количество ошибок до уровня, близкого к 0.
Для этого необходимо перепроверять способом, отличным от прямого вычисления.
Примерно, как проверяют найденные корни уравнения подстановкой их значений в уравнение и проверкой, что знак равенства выполняется.
В этом случае работает перемножение довольно малых вероятностей.
Если вероятность ошибиться как при прямом вычислении, так и при перепроверке — 10%, то общая вероятность после их перемножения даёт всего 1%, что явно лучше 10%.
Если исходные вероятности ещё меньше, то эффект — ещё значительнее.
Можно и вторую перепроверку использовать каким-нибудь третьим способом, чтобы ещё уменьшить результирующую вероятность ошибиться, но обычно достаточно одной перепроверки.
А совсем без перепроверки ошибки обычно слишком часты.
Плохо знакомы, не перепроверяете.
12 Вольт, 2 Ома, по закону Ома ток — 6 Ампер.
12 Вольт, 6 Ампер, 72 Ватта.
Вот вам и 300 Ватт.
Меньше полуОма там сопротивление.
Вы немного не дошли до цели: там серебристый множитель, а не золотистый.
Поэтому там — 0.1 Ома, а это — уже совсем другое дело.
А что мешает?
А также при выборе значения из двух выражений.
Хорошо, что там, всего лишь,
z
.А если бы было так?
Приходится высматривать, а одинаковые ли имена в ветке
if
и в веткеelse
или нет.Когнитивная нагрузка здесь — только с непривычки.
Уж лучше тогда кнопку "КолотиБабло".
Значит, для C++ единственный выход — использовать функцию конверсии.
На C++:
Отдельно на C:
потом с'link'овать вместе, вызывать из C++.
Вот здесь очень с умолчанием написано:
Непонятно насчёт разыменования, но, поскольку для pointer to any character type явно подразумевается разыменование, хотя явным образом упорно об этом умалчивается, то и для преобразованного pointer to object логично ожидать разрешения на разыменование, лишь бы выравнивание было корректным.
Соответственно, аргумент:
не состоятелен.
А вот здесь написано, что совсем в другом году.
Не знал, что тикер EURUSD нынче перевернули зачем-то.
Странно, почему тогда у брокеров отличия в котировках настолько малы, что условно можно считать их совпадающими?
Верно.
Одновременно с этим у массива, в отличие от структуры, нет своей
const
/volatile
-ности, значение массива, в отличие от значения структуры, нельзя присвоить значению однотипного массива, а также нельзя инициализировать.Как следствие, в отличие от структуры, его нельзя по значению ни передать в функцию, ни вернуть оттуда.
При этом, что интересно, в составе структуры он прекрасно может быть использован во всех этих качествах, но — только от имени структуры.
Именно поэтому массив — это не "нетип", а именно "недотип".
Это — для того, чтобы иметь возможность дать ответ в общем случае, а не гадать.
Без описания того, как всё устроено, нельзя дать ответ в общем случае.
Хорошо, я изменю оригинальный пример с cppreference, создав массив не из 2-х подмассивов, а из одного:
Теперь, по логике того объяснения, которое было в оригинальном примере, здесь UB нет, верно?
Говорит ли применённое мной выражение
i[&**a]
о том, что я этого не понимаю?Я проверил один пример на pure C:
Печатается:
Какая-то некачественная выходит нейтрализация.
С этого момента попрошу поподробнее.
В C/C++ нет многомерных массивов, зато есть массивы массивов.
И сам тип массив является "недотипом": один однотипный массив нельзя присвоить другому, в отличие от тех же структур, также массив нельзя инициализировать однотипным массивом.
В этом смысле объектов типа "массив" не существует.
Косвенно это можно обнаружить по тому, что квалификаторы
const
/volatile
к самому массиву не применимы, только к типам самих элементов.Опять же, — в отличие от тех же структур.
В чём же может быть отличие между "одномерным" и "многомерным" массивом, учитывая вышесказанное, а также, учитывая, что элементы лежат "плотно"?
В стандарте очень часто — головоломка, потому что не описывается, что лежит в основе, но, кажется, я начинаю догадываться, что они могли иметь ввиду, хотя это также может быть неполным пониманием.
Если считать, что должны поддерживаться архитектуры, в которых доступ к памяти защищён по типу сегментов в IA-32 и принять, что при инициализации
p0
выше "выдаётся" указатель, который безопасно можно разыменовывать только какint
'ы, и — в пределах 4int
'ов в памяти, считая от начала памяти, занимаемойarr[0]
, и двигать, но не разыменовывать на 5-ый, иначе возникнет исключение общей защиты памяти, то дальше получается следующее.При инициализации
p1
выдаётся аналогичный указатель, но начало "сегмента" находится по адресуarr[1]
.Тогда сравнение
p0 == p1
, в котором сравнивается только адресная составляющая, "срабатывает" как раз в тот момент, когдаp0
ещё может иметь такое значение, но разыменовывать его уже нельзя.При присваивании в теле
if
'а вp0
записывается полная информация изp1
: не только информация об адресе, которая в этот момент совпадает, но и информация о том, какой сегмент памяти доступен без срабатывания общей защиты памяти.И поэтому при наличии этого
if
'а дальнейшие итерации в цикле не приводят к срабатыванию этой защиты, то есть, UB, а без него однозначно привели бы.Если продолжать рассуждать в этом смысле, то следующий код, по идее:
должен создать указатель с адресом, равным адресу, по которому лежит исходный массив
arr
, доступом к объектам типаint
и защитой в виде "сегмента", настроенного на все 8 элементов исходного массива.И тогда проблем с одномерным доступом к всему массиву не возникает.
Но они не говорят, как всё устроено, и что за всеми этими штуками стои́т.
Про это там тоже написано.
То есть, они неявно полагают, что можно адресную арифметику здесь применить, и на этом основании утверждать, что доступно?
Если снять одно измерение и повторить рассуждения, то получается следующее:
Или так — можно:
А так — UB?
Без знания подоплёки не понять.
Здесь важнее то, где он примени́м и как.
Я пытался ознакомиться с самой сутью, но мне это не удаётся сделать.
Именно этот пример вызвал наибольшие затруднения.
Как
x2[1]
может быть доступен через указатель на массив из 10 элементов?Скорее, это намекает на то, что сначала допустили какую-то ошибку, а теперь лихорадочно пытаются костылить, чтобы эту ошибку обойти.
Предназначены они, по задумке, могут быть и для другого.
Важно, для чего они могут быть использованы в принципе.
В частности, если они годятся для решения обсуждаемого вопроса, то не имеет значения, для чего они были задуманы изначально.
В C, начиная с C99, — можно:
В C++ — да, нельзя.
Это верно в отсутствие
std::launder
.Думаю, что конкретно этот "трюк" ограничен избыточно.
Не уследили за компиляторописателями.
Хотя, — вот вариант, который обещает быть рабочим:
Говорят, что пока ещё ни один из компиляторов не поддерживает ни
start_lifetime_as
, ниstart_lifetime_as_array
.Однако, само название намекает, что данные "трюки" настолько востребованы, что для них даже добавили специальные функции.
Для оптимизаций.
Видите, как здесь gcc мощно соптимизировал?
Всплывающее описание для инструкции процессора
ud2
:"Generates an invalid opcode exception. This instruction is provided for software testing to explicitly generate an invalid opcode exception. The opcodes for this instruction are reserved for this purpose."
Правда, я боюсь, что теперь вы будете ругаться ещё сильнее.
Ну,
constexpr
— это особая ипостась, преобразовать не даёт.Через
reinterpret_cast
— потому что становится неconstexpr
, а через двойнойstatic_cast
черезvoid *
— сначала потому что "cast from 'void *' is not allowed in a constant expression in C++ standards before C++2c", а потом, после включения C++2c, — потому что "cast from 'void *' is not allowed in a constant expression because the pointed object type 'int[2][4]' is not similar to the target type 'int[8]'".Наверное, следует подождать, когда
reinterpret_cast
станет доступен вconstexpr
-функциях.Я модифицировал ваш пример:
Получается "read of dereferenced one-past-the-end pointer is not allowed in a constant expression".
Тогда я добавил ещё один указатель и очень интересный
if
:Этот
if
- бессмысленный, потому что выполнение его тела ничего не меняет:Однако, без него наблюдается "one-past-the-end pointer is not allowed".
Здесь у меня возникло какое-то ощущение, что компиляторостроители перегнули палку в этом месте, борясь за пространство для оптимизаций.
Мне не удалось найти способа для
constexpr
-функций получить чистое решение для линейного доступа ко всему объекту.Для C++, видимо, действительно, необходимо преобразование:
Не уверен, требуется ли здесь "отмывание" с помощью
std::launder
, ибо эта функция как будто специально описана так непонятно, что без тайного знания понять невозможно.Для C, возможно, тип тоже должен приводиться (если разыменование и сразу же взятие адреса
&x[0]
для C не "отвязывает" указатель от подобъекта в том смысле, что "отвязанным" указателем можно "гулять" по всему объекту):Но, в любом случае, это не должно быть явное
a[0][i]
, где нарушение границ налицо.Почему в PVS-Studio никто из экспертов по C/C++ не вычитывает и не проводит review ваших статей перед публикацией?
Конечно же, нет.
Тип выражения
a[0]
отличается от указателя наint[4]
.Неправильным здесь является способ использования многомерного массива как одномерного.
Именно поэтому следует получить адрес первого элемента и проиндексировать его:
Если этот код непонятен, можно попроще:
Если и этот непонятен, можно ещё проще:
В последнем варианте явным образом видно, что сначала был взят адрес первого элемента, который был тут же проиндексирован — ровно то, о чём я написал перед первым примером.
Ни один компилятор — не то, что не ругался на UB, но даже и не пикнул: все компиляторы сгенерировали правильный код, который отработал и напечатал ожидаемую последовательность.
Это — результат правильного способа использования многомерного массива как одномерного.