Этот вопрос касается указателей, полученных с использованием арифметики указателей со структурными смещениями.
Рассмотрим следующую программу:
#include <cstddef>
#include <iostream>
#include <new>
struct A {
float a;
double b;
int c;
};
static constexpr auto off_c = offsetof(A, c);
int main() {
A * a = new A{0.0f, 0.0, 5};
char * a_storage = reinterpret_cast<char *>(a);
int * c = reinterpret_cast<int *>(a_storage + off_c));
std::cout << *c << std::endl;
delete a;
}
Эта программа работает и дает ожидаемые результаты на протестированных мной компиляторах, используя настройки по умолчанию и стандарт C ++ 11.
(Тесно связанная программа, где мы используем void *
вместо char *
а также static_cast
вместо reinterpret_cast
,
не является общепринятым. gcc 5.4
выдает предупреждение об арифметике указателей с пустыми указателями, и clang 6.0
говорит, что указатель
арифметика с void *
это ошибка.)
Имеет ли эта программа четко определенное поведение в соответствии со стандартом C ++?
Зависит ли ответ от того, имеет ли реализация ослабленная или строгая безопасность указателя ([basic.stc.dynamic.safety]
)?
В вашем коде нет фундаментальных ошибок.
Если A
это не простые старые данные, выше UB (до C ++ 17) и условно поддерживается (после C ++ 17).
Вы можете заменить char*
а также int*
с auto*
но это вещь стиля.
Обратите внимание, что указатели на члены делают это точно так же безопасным для типов способом. Большинство компиляторов реализуют указатель на член … как смещение члена в типе. Они, однако, работают везде, даже на структурах, не являющихся стручками.
В сторону: я не вижу гарантии, что offsetof
является constexpr
в стандарте. 😉
В любом случае замените:
static constexpr auto off_c = offsetof(A, c);
с
static constexpr auto off_c = &A::c;
а также
auto* a_storage = static_cast<char *>(a);
auto* c = reinterpret_cast<int *>(a_storage + off_c));
с
auto* c = &(a->*off_c);
сделать это способом C ++.
Это безопасно в вашем конкретном примере, но только потому, что ваша структура стандартное расположение, который вы можете перепроверить, используя std::is_standard_layout<>
,
Попытка применить это к структуре, такой как:
struct A {
float a;
double b;
int c;
std::string str;
};
Было бы недопустимо, даже если строка прошла часть соответствующей структуры.
редактировать
Вот что меня интересует abt: в 3.7.4.3 [basic.stc.dynamic.safety] говорится, что указатель безопасно получен, только если (условия) и если у нас строгая безопасность указателя, тогда указатель недействителен, если он не прибыл из такого места , В 5.7 арифметике указателей говорится, что я могу выполнять обычную арифметику в массиве, но я не вижу там ничего, говорящего мне, что арифметика смещения структуры в порядке. Я пытаюсь выяснить, не относится ли это к делу так, как я думаю, или если арифметика смещения структуры не подходит для гипотетических «строгих» имплов, или если я прочитал 5.7 неправильно (n4296)
Когда вы делаете арифметический указатель, вы выполняете его на массиве char
размером не менее sizeof(A)
так что все в порядке.
Затем, когда вы возвращаетесь ко второму участнику, вы попадаете под (2.4):
— результат четко определенного преобразования указателя (4.10, 5.4) из безопасного значения указателя;
Вы должны проверить свои предположения.
Предположение № 1) offsetof дает правильное смещение в байтах. Это гарантируется только в том случае, если класс считается «стандартным макетом», который имеет ряд ограничений, таких как отсутствие виртуальных методов, исключает множественное наследование и т. Д. В этом случае все должно быть в порядке, но в целом вы не можете быть уверенным.
Предположение № 2) Символ имеет тот же размер, что и байт. В C это по определению, так что вы в безопасности.
Предположение № 3) Параметр offsetof дает правильное смещение от указателя на класс, а не от начала данных. Это в основном то же самое, что и # 1, но vtable, безусловно, может быть проблемой. Опять же, работает только со стандартным макетом.