Obtenho sizeof()
resultados estranhos [[no_unique_address]]
no g++ 13.2.
#include <iostream>
using namespace std;
template<bool T>
struct B
{
struct Dummy {};
int *a; // swap position with <below placemark>
// In this position, results are "16" and "40".
[[no_unique_address]] std::conditional_t<T, Dummy, size_t> y;
[[no_unique_address]] std::conditional_t<T, Dummy, size_t> x;
[[no_unique_address]] std::conditional_t<T, Dummy, size_t> h;
[[no_unique_address]] std::conditional_t<T, Dummy, size_t> w;
// <below placemark>
// In this position, results are "8" and "40".
};
int main()
{
cout << sizeof(B<true>) << "\n";
cout << sizeof(B<false>) << "\n";
}
O resultado é
16
40
mas se você mudar int *a;
para a posição após as [[no_unique_address]]
linhas, você obterá
8
40
Além disso, com apenas uma [[no_unique_address]]
linha e com a variável de ponteiro de membro antes, os resultados estão corretos:
8
16
Eu li a documentação, [[no_unique_address]]
mas não faço nada específico.
Por que isso acontece?
A resposta insatisfatória é que
[[no_unique_address]]
é apenas uma dica para o compilador de que ele pode sobrepor os membros da estrutura, mas não garante nenhum layout específico. (Do padrão: “as circunstâncias sob as quais o objeto tem tamanho zero são definidas pela implementação”).Existem algumas maneiras pelas quais o compilador é restrito na forma como pode alocar deslocamentos de campo. Uma é que os deslocamentos de campo não devem ser decrescentes. Outra é que os campos do mesmo tipo devem ter deslocamentos distintos (caso contrário, os ponteiros para campos diferentes não seriam distinguíveis — isso não é um problema para ponteiros para campos de tipos diferentes porque regras estritas de alias proíbem essas comparações).
Isso significa que se você definiu uma estrutura com 4 estruturas fictícias, essa estrutura ainda ocupará 4 bytes porque os campos devem receber deslocamentos diferentes.
Isso significa concretamente que , embora o compilador possa atribuir
a
oy
mesmo deslocamento, ele deve atribuirx
e deslocamentos distintos. Isso pode ser demonstrado do seguinte modo:h
w
impressões:
Aparentemente, o compilador decide que
x
o deslocamento não deve se sobrepor aa
ouy
. Não está claro para mim se isso é uma peculiaridade do compilador ou se há uma boa razão para isso (vale a pena notar que GCC e Clang se comportam da mesma maneira). O tamanho total de 16 é então o resultado do arredondamento do tamanho de 11 bytes para um múltiplo de 8.Parece haver duas maneiras de evitar isso. Uma é colocar os campos vazios primeiro. Outra é garantir que eles tenham tipos diferentes:
impressões:
Acho que a solução mais simples é colocar todos os campos potencialmente vazios no topo da estrutura.
Quando
T
étrue:
sizeof(a)
é 8.sizeof(x)
... são iguaissizeof(Dummy)
e são 1 cada.x
,y
,h
,w
podem se sobrepor e seu tamanho de soma pode ser de 1 a 4, seja 1.sizeof(B<true>)
ésizeof(a)
+sizeof(x) ...
+ pad(7) é 8 + 1 + 7 é 16.Quando você se move
a
na parte inferior:x
,y
,h
,w
permitem ser sobrepostos pora
. Como estão vazios, eles estão completamente sobrepostos pora
.sizeof(B<true>)
está sobreposto (0) +sizeof(a)
é 8.Por que o primeiro caso realmente não se sobrepõe
x
ao tamanho zero no final do armazenamento da classe. Considere uma matriz . member permite ser sobreposto por um próximo membro da classe, mas não permite ser sobreposto por . Se fosse 8, iria se sobrepor , isso não era permitido.y
h
w
B<true> arr[2]
[[no_unique_address]]
arr[1].a
sizeof(B<true>)
arr[1].a
arr[0].w