gcc /clang размещает поля производной структуры в отступе базовой структуры [дубликаты]

    

На этот вопрос уже есть ответ здесь:

    

Меня смущает то, как gcc и clang выстраивают структуры, когда задействованы как padding, так и наследование. Вот пример программы:

#include <string.h>
#include <stdio.h>

struct A
{
    void* m_a;
};

struct B: A
{
    void* m_b1;
    char m_b2;
};

struct B2
{
    void* m_a;
    void* m_b1;
    char m_b2;
};

struct C: B
{
    short m_c;
};

struct C2: B2
{
    short m_c;
};

int main ()
{
    C c;
    memset (&c, 0, sizeof (C));
    memset ((B*) &c, -1, sizeof (B));

    printf (
        "c.m_c = %d; sizeof (A) = %d sizeof (B) = %d sizeof (C) = %d\n", 
        c.m_c, sizeof (A), sizeof (B), sizeof (C)
        );

    C2 c2;
    memset (&c2, 0, sizeof (C2));
    memset ((B2*) &c2, -1, sizeof (B2));

    printf (
        "c2.m_c = %d; sizeof (A) = %d sizeof (B2) = %d sizeof (C2) = %d\n", 
        c2.m_c, sizeof (A), sizeof (B2), sizeof (C2)
        );

    return 0;
}

Вывод:

$ ./a.out
c.m_c = -1; sizeof (A) = 8 sizeof (B) = 24 sizeof (C) = 24
c2.m_c = 0; sizeof (A) = 8 sizeof (B2) = 24 sizeof (C2) = 32

Структуры C1 и C2 расположены по-разному. В C1 m_c размещается в отступе структуры B1 и поэтому перезаписывается 2-й memset (); с С2 этого не происходит.

Используемые компиляторы:

$ clang --version
Ubuntu clang version 3.3-16ubuntu1 (branches/release_33) (based on LLVM 3.3)
Target: x86_64-pc-linux-gnu
Thread model: posix

$ c++ --version
c++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

То же самое происходит с опцией -m32 (очевидно, что размеры выходных данных будут разными).

В обеих версиях компилятора Microsoft Visual Studio 2010 C ++ для x86 и x86_64 такой проблемы нет (т. е. они одинаково выкладывают структуры С1 и С2)

Если это не ошибка и она задуманна, тогда у меня следующие вопросы:

  1. каковы точные правила для распределения или не выделения полей производной структуры в отступах (например, почему это не происходит с C2?)
  2. есть ли способ переопределить это поведение с помощью переключателей /атрибутов (т.е. выложить так же, как MSVC)?

Заранее спасибо.

Владимир

7 голосов | спросил user3922059 10 AM000000110000004531 2014, 11:07:45

1 ответ


0

Для всех, кто отрицательно отзывается на этот вопрос и на самопожертвование ОП с самодовольным негодованием по поводу того, как ужасно UB его рукописный memcpy. .. учтите, что разработчики как libc ++, так и libstdc ++ попадают в одну и ту же яму. В обозримом будущем на самом деле очень важно понять, когда хвостовое заполнение используется повторно, а когда - нет. Хорошо на OP для поднятия этой проблемы.

Правила Itanium ABI для разметки структуры находятся здесь . Соответствующая формулировка

  

Если D является базовым классом, обновите sizeof (C) до max (sizeof (C), offset (D) + nvsize (D)).

Здесь «dsize, nvsize и nvalign [типов POD] определены как их обычный размер и выравнивание», но nvsize не-типа POD определяется как «не виртуальный размер объекта, который имеет размер O без виртуальных оснований [и также без дополнения хвостом]. " Так что, если D - POD, мы никогда ничего не вкладываем в его хвостовую подкладку; тогда как, если D является не POD, нам разрешается втиснуть следующий элемент (или базу) в его хвостовой отступ.

Следовательно, любой не POD-тип (даже тривиально копируемый!) должен учитывать возможность того, что он имеет важные данные, вставленные в его хвостовую часть. Как правило, это нарушает предположения разработчиков о том, что допустимо делать с тривиально копируемыми типами (а именно, что вы можете тривиально копировать их).

Контрольный пример Wandbox:

#include <algorithm>
#include <stdio.h>

struct A {
    int m_a;
};

struct B : A {
    int m_b1;
    char m_b2;
};

struct C : B {
    short m_c;
};

int main() {
    C c1 { 1, 2, 3, 4 };
    B& b1 = c1;
    B b2 { 5, 6, 7 };

    printf("before operator=: %d\n", int(c1.m_c));  // 4
    b1 = b2;
    printf("after operator=: %d\n", int(c1.m_c));  // 4

    printf("before std::copy: %d\n", int(c1.m_c));  // 4
    std::copy(&b2, &b2 + 1, &b1);
    printf("after std::copy: %d\n", int(c1.m_c));  // 64, or 0, or anything but 4
}
ответил Quuxplusone 14 J000000Saturday18 2018, 05:27:32

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132