Имеет ли следующий код (который выполняет арифметику указателей на границах подобъектов) корректное поведение для типов T
, для которых он компилируется (что в C++ 11, does not not necessarily have to be 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 и as - ((P) - ((Q) +1)), и имеет значение 0, если выражение P точек один за последним элементом объекта массива, хотя выражение (Q) +1 не указывает на элемент объекта массива. ... Если оба указателя не указывают на элементы одного и того же объекта массива или один за последним элементом объекта массива, поведение не определено.
Но теоретически, средняя часть вышеприведенной цитаты, в сочетании с макетом класса и выравниванием гарантиями, может позволить следующему (незначительная) регулировки в силе:
#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
не было?
ПОСЛЕДНИЙ EDIT @ArneMertz выступает за очень внимательное прочтение N3242/expr.add (да, я знаю, что я читаю проект, но это достаточно близко), но делает стандарт действительно подразумевается, что следующее имеет неопределенное поведение, если линия подкачки удалена? (определения же класса, что и выше)
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))))
Является ли код, предполагающий, что два равных указателя указывают на один и тот же объект массива, действительно нарушенный в соответствии со строгим чтением стандарта?
EDIT К сожалению, просто хочу добавить еще один пример, чтобы устранить стандартный вопрос компоновки:
#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;
}
Не могу поверить, что в теге C++ есть хорошие вопросы. +1. –
Возможно, дубликат [выравнивание элементов Союза] (http://stackoverflow.com/questions/891471/union-element-alignment), но я не уверен –
@ BЈовић этот вопрос предполагает понимание ответа на этот вопрос, на самом деле –