Czy następujący kod (który wykonuje arytmetyczną wskazówkę na granicach podobiektów) ma dobrze zdefiniowane zachowanie dla typów T
, dla których kompiluje (który w C++ 11, does not not necessarily have to be POD) lub jego podzestawie?Arytmetyka wskaźnikowa na granicach podobiektów
#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 wykorzystuje wariancie powyższej techniki w celu wykonania wewnętrznego rodzaju wektora, który jest zoptymalizowany do wstępnego użyciu stosu małych tablic, ale przełącza się bufor sterty przydzielana na początkowej mocy. (Powodem robi to w ten sposób nie wynika z tego przykładu, ale to widocznie zmniejszyć kod szablonu uwędzić, to jest wyraźniejsze, jeśli spojrzeć przez code).
UWAGA: Przed ktoś narzeka, to nie jest dokładnie co robią i być może ich podejście jest bardziej zgodne z normami niż to, co tu podałem, ale chciałem zapytać o ogólny przypadek.
Oczywiście działa to w praktyce, ale jestem ciekawy, czy cokolwiek w standardowej gwarancji, aby tak było. Jestem skłonny powiedzieć nie, biorąc pod uwagę N3242/expr.add:
Gdy dwa wskaźniki do elementów tego samego obiektu tablicy są odejmowane, wynik jest różnicą indeksów dwóch elementów tablicy. ..Ponadto, jeśli wyrażenie P wskazuje na element obiektu tablicy lub jeden za ostatnim elementem obiektu tablicy, a wyrażenie Q wskazuje na ostatni element tego samego obiektu tablicy, wyrażenie ((Q) +1) - (P) ma taką samą wartość jak ((Q) - (P)) + 1 i as - ((P) - ((Q) +1)) i ma wartość zero, jeśli wyrażenie P punktów jeden za ostatnim elementem obiektu tablicy, mimo że wyrażenie (Q) +1 nie wskazuje na element obiektu tablicy. ... Jeśli oba wskaźniki nie wskazują elementów tego samego obiektu tablicy lub jednego ostatniego elementu obiektu tablicy, zachowanie jest niezdefiniowane.
Ale teoretycznie, środkowa część z powyższym cytatem, w połączeniu z układem klasy i wyrównania gwarancji, może pozwolić następujące (minor) regulacja ważność:
#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;
}
co w połączeniu z różnymi innymi przepisami w odniesieniu do układu union
, wymienialność na i od char *
itp., może prawdopodobnie sprawić, że oryginalny kod również będzie ważny. (Głównym problemem jest brak przechodniości w definicji arytmetyki wskaźnika podanej powyżej.)
Ktoś wie na pewno? N3242/expr.add wydaje się jasne, że wskaźniki muszą należeć do tego samego "obiektu tablicowego", aby można go było zdefiniować, ale może to być np. Fakt, że inne gwarancje w standardzie, gdy są ze sobą połączone, mogą wymagać definicję w każdym przypadku w tym przypadku, aby zachować logiczną samowystarczalność. (Nie mam zagrać na nim, ale byłoby to co najmniej do pomyślenia.)
EDIT: @MatthieuM podnosi zarzut, że ta klasa nie jest standardowym układ i dlatego nie może być zagwarantowane nie zawierają dopełnienie między podstawowy podobiekt i pierwszy element wyprowadzonego, nawet jeśli oba są wyrównane do alignof(T)
.Nie jestem pewien, jak to jest prawdziwe, ale otwiera się na następujące pytania Wariant:
byłoby to gwarantowane do pracy, czy dziedziczenie zostały usunięte?
Czy można uzyskać
&d.end - &d.begin >= sizeof(float) * 10
, nawet jeśli&d.end - &d.begin == sizeof(float) * 10
nie było?
OSTATNIA EDYCJA @ArneMertz przemawia za bardzo uważnej lektury N3242/expr.add (tak, wiem, czytam projekt, ale jest to na tyle blisko), ale robi naprawdę standard sugeruje, że następujące zachowanie ma niezdefiniowane zachowanie, jeśli linia wymiany zostanie usunięta? (definicje samej klasy jak wyżej)
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;
}
Ponadto, jeśli ==
nie jest wystarczająco silny, co będzie, jeśli skorzystamy z faktu, że std::less
tworzy sumie uporządkowane nad wskaźnikami i zmienić tryb warunkowy wyżej:
if((aligned = (!std::less<float *>()(p, q) && !std::less<float *>()(q, p))))
Czy kod zakłada, że dwa równe wskaźniki wskazują na ten sam obiekt tablicy, który jest naprawdę uszkodzony zgodnie ze ścisłym odczytem standardu?
EDIT Niestety, po prostu chcą dodać jeszcze jeden przykład, aby wyeliminować problem standardowy układ:
#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;
}
Nie mogę uwierzyć, że w tagu C++ są dobre pytania. +1. –
Może duplikat [Unia wyrównanie elementu] (http://stackoverflow.com/questions/891471/union-element-alignment), ale nie jestem pewien –
@ BЈовић to pytanie zakłada zrozumienie odpowiedzi na to pytanie, a właściwie –