Смежно распределенные и последовательно выделенные - PullRequest
0 голосов
/ 27 февраля 2019

Стандартный раздел C11 6.2.5.20 определяет массив как:

Тип массива описывает непрерывно распределенный непустой набор объектов с конкретным типом объекта-члена, называемым типом элемента.

, в то время как структура определена как:

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

В разделе 6.7.2.1 говорится, что заполнение может быть вставлено между полями:

Каждый элемент не битового поля структуры илиобъект объединения выравнивается определенным реализацией способом, соответствующим его типу.

Внутри объекта структуры члены, не являющиеся битовыми полями, и блоки, в которых находятся битовые поля, имеют адреса, которые увеличиваются в порядке вкоторые они объявлены.Указатель на объект структуры, соответствующим образом преобразованный, указывает на его начальный элемент (или, если этот элемент является битовым полем, затем на модуль, в котором он находится), и наоборот.Внутри объекта структуры может быть безымянный отступ, но не в его начале.

Но означает ли это, что следующие объекты могут иметь разную структуру памяти?

struct A {
    char x0;
    short x1;
};

struct B {
    struct A x0;
    struct A x1;
    struct A x2;
};

assert(sizeof(struct B) == sizeof(struct A[3]));

Iсоздал этот тестовый скрипт для проверки макета памяти для GCC:

import itertools
import subprocess

src = """
#include "assert.h"

struct A {
{fields}
};

struct B {
    struct A x0;
    struct A x1;
    struct A x2;
};

int main(int argc, char** argv) {
    assert(sizeof(struct B) == sizeof(struct A[3]));
    return 0;
}
"""

def main():
    all_types = ["char", "short", "int", "long long"]

    for types in itertools.product(all_types, repeat=3):
        rendered = src.replace("{fields}", "".join([
            "        {} x{};\n".format(t, i)
            for i, t in enumerate(types)]))
        with open("main.c", "w") as f:
            f.write(rendered)
        subprocess.call(["gcc", "main.c"])
        subprocess.call(["./a.out"])

if __name__ == "__main__":
    main()

Но GCC всегда создает одинаковую схему памяти для массива и структуры.

  • Существуют ли примеры из реальной жизни, когда макет отличается?
  • Безопасно ли приводить такой экземпляр структуры к массиву?
  • Было бы безопаснее ссоюз?

1 Ответ

0 голосов
/ 27 февраля 2019

Разница заключается в том, что массив, два элемента должны быть смежными, без чередующего заполнения, в то время как в структуре они последовательные, но заполнение может присутствовать в определенном для реализации виде.

Теперь по вашим вопросам:

Есть ли примеры из реальной жизни, когда макет отличается?

AFAIK, не с обычными компиляторами.Кроме того, у большинства есть опции, с помощью которых программист может попросить не добавлять заполнение в структуру.

Безопасно ли приводить такой экземпляр структуры к массиву?

Нет, потому что структура не объявляет эквивалентный массив, а одна переменная может быть привязана только к массиву размером 1. Так что a - это одна переменная, *(&a + 1) - формально неопределенное поведение.

Было бы безопаснее с объединением?

Да, в соответствии с , что другие SO сообщения это может быть сделано через объединение.Это допустимо C:

union B {
    struct {
        struct A x0;
        struct A x1;
        struct A x2;
    };
    struct A x[3];
};

Даже если стандарт не гарантирует этого, общий компилятор никогда не добавляет заполнение между элементами одного типа, будь то тип простой или производный (структура).Причина та же, что и для первого вопроса.

...