Имеет ли следующий код (который выполняет арифметику указателей на границах подобъектов) корректное поведение для типов T
, для которых он компилируется (что в С++ 11, необязательно должен быть POD) или его подмножеством?
#include <cassert>
#include <cstddef>
template<typename T>
struct Base
{
// ensure alignment
union
{
T initial;
char begin;
};
};
template<typename T, size_t N>
struct Derived : public Base<T>
{
T rest[N - 1];
char end;
};
int main()
{
Derived<float, 10> d;
assert(&d.rest[9] - &d.initial == 10);
assert(&d.end - &d.begin == sizeof(float) * 10);
return 0;
}
LLVM использует вариацию вышеупомянутого метода при реализации внутреннего векторного типа, который оптимизирован для первоначального использования стека для небольших массивов, но переключается на буфер, распределенный по кучам, после первоначальной емкости. (Причина этого не очевидна из этого примера, но, по-видимому, для уменьшения размытости кода шаблона, это яснее, если вы просмотрите code.)
ПРИМЕЧАНИЕ.. Прежде чем кто-либо пожалуется, это не совсем то, что они делают, и может быть, что их подход более соответствует стандартам, чем то, что я здесь дал, но я хотел спросить о общий случай.
Очевидно, что это работает на практике, но мне любопытно, если что-либо в стандартных гарантиях для этого. Я склонен сказать "нет", учитывая N3242/expr.add:
Когда два указателя на элементы одного и того же объекта массива вычитаются, результатом является разность индексов двух элементов массива... Более того, если выражение P указывает либо на элемент объекта массива, либо на одно прошлое последний элемент объект массива, а выражение Q указывает на последний элемент одного и того же объекта массива, выражение ((Q) +1) - (P) имеет то же значение, что и ((Q) - (P)) + 1, и как - ((P) - ((Q) +1)) и имеет значение 0, если выражение P указывает один за последним элементом объекта массива, хотя выражение (Q) +1 не указывает на элемент объекта массива.... Если оба указателя не указывают на элементы одного и того же объекта массива или один за последним элементом объекта массива, поведение undefined.
Но теоретически средняя часть приведенной выше цитаты, в сочетании с макетом класса и гарантиями выравнивания, может разрешить следующую (младшую) настройку:
#include <cassert>
#include <cstddef>
template<typename T>
struct Base
{
T initial[1];
};
template<typename T, size_t N>
struct Derived : public Base<T>
{
T rest[N - 1];
};
int main()
{
Derived<float, 10> d;
assert(&d.rest[9] - &d.rest[0] == 9);
assert(&d.rest[0] == &d.initial[1]);
assert(&d.rest[0] - &d.initial[0] == 1);
return 0;
}
который в сочетании с различными другими положениями, касающимися макета union
, конвертируемости в char *
и т.д., может, возможно, также сделать исходный код действительным. (Основная проблема заключается в отсутствии транзитивности в определении арифметики указателя, приведенной выше.)
Кто-нибудь знает наверняка? N3242/expr.add, по-видимому, ясно указывает, что указатели должны принадлежать к одному и тому же "объекту массива" для его определения, но гипотетически может быть, что другие гарантии в стандарте, когда они объединены вместе, могут потребовать определения в любом случае в этот случай, чтобы оставаться логически самосогласованным. (Я не ставлю на него, но я бы это по крайней мере мыслил.)
EDIT: @MatthieuM выдвигает возражение о том, что этот класс не является стандартным макетом и, следовательно, не может быть гарантированно не содержать отступов между базовым подобъектом и первым членом производного, даже если оба выровнены с alignof(T)
. Я не уверен, насколько это верно, но это открывает следующие варианты вопросов:
-
Будет ли гарантировано работать, если удаление было удалено?
-
Было бы гарантировано
&d.end - &d.begin >= sizeof(float) * 10
, даже если&d.end - &d.begin == sizeof(float) * 10
не были?
LAST EDIT @ArneMertz утверждает, что очень внимательно читает N3242/expr.add(да, я знаю, что я читаю черновик, но он достаточно близко), но действительно ли стандарт подразумевает что следующее имеет поведение undefined, если линия подкачки удалена? (такие же определения классов, как указано выше)
int main()
{
Derived<float, 10> d;
bool aligned;
float * p = &d.initial[0], * q = &d.rest[0];
++p;
if((aligned = (p == q)))
{
std::swap(p, q); // does it matter if this line is removed?
*++p = 1.0;
}
assert(!aligned || d.rest[1] == 1.0);
return 0;
}
Кроме того, если ==
недостаточно силен, что, если мы воспользуемся тем фактом, что std::less
образует полный порядок над указателями и меняет условное выражение на:
if((aligned = (!std::less<float *>()(p, q) && !std::less<float *>()(q, p))))
Является ли код, предполагающий, что два равных указателя указывают на тот же самый объект массива, который действительно нарушен в соответствии со строгим чтением стандарта?
РЕДАКТИРОВАТЬ Извините, просто добавьте еще один пример, чтобы устранить проблему стандартного макета:
#include <cassert>
#include <cstddef>
#include <utility>
#include <functional>
// standard layout
struct Base
{
float initial[1];
float rest[9];
};
int main()
{
Base b;
bool aligned;
float * p = &b.initial[0], * q = &b.rest[0];
++p;
if((aligned = (p == q)))
{
std::swap(p, q); // does it matter if this line is removed?
*++p = 1.0;
q = &b.rest[1];
// std::swap(p, q); // does it matter if this line is added?
p -= 2; // is this UB?
}
assert(!aligned || b.rest[1] == 1.0);
assert(p == &b.initial[0]);
return 0;
}