Выровнен ли адрес malloc + size_t * 3 для любого типа? - PullRequest
3 голосов
/ 26 апреля 2019

Я создаю своего рода динамический массив (вектор), но вместо того, чтобы встраивать данные (обычно void *) в struct vector, я резервирую пространство для struct vector + блока байтов, пример использования массива size_t s:

#include <stdio.h>
#include <stdlib.h>

struct vector {
    size_t capacity;
    size_t typesize;
    size_t size;
};

#define VECTOR(v) ((struct vector *)((unsigned char *)v - sizeof(struct vector)))

static void *valloc(size_t typesize, size_t size)
{
    struct vector *vector;
    unsigned char *data;

    data = calloc(1, sizeof(*vector) + typesize * size);
    if (data == NULL) {
        return NULL;
    }
    vector = (struct vector *)data;
    vector->typesize = typesize;
    vector->capacity = size;
    vector->size = 0;
    return data + sizeof(*vector);
}

static void *vadd(void *data)
{
    struct vector *vector = VECTOR(data);
    unsigned char *new;
    size_t capacity;

    if (vector->size >= vector->capacity) {
        capacity = vector->capacity * 2;
        new = realloc(vector, sizeof(*vector) + vector->typesize * capacity);
        if (new == NULL) {
            return NULL;
        }
        vector = (struct vector *)new;
        vector->capacity = capacity;
        vector->size++;
        return new + sizeof(*vector);
    }
    vector->size++;
    return data;
}

static size_t vsize(void *data)
{
    return VECTOR(data)->size;
}

static void vfree(void *data, void (*func)(void *))
{
    struct vector *vector = VECTOR(data);

    if (func != NULL) {
        for (size_t iter = 0; iter < vector->size; iter++) {
            func(*(void **)((unsigned char *)data + iter * vector->typesize));
        }
    }
    free(vector);
}

int main(void)
{
    size_t *data;

    data = valloc(sizeof(size_t), 1);
    if (data == NULL) {
        perror("valloc");
        exit(EXIT_FAILURE);
    }
    for (size_t i = 0; i < 10; i++) {
        data = vadd(data);
        if (data == NULL) {
            perror("vadd");
            exit(EXIT_FAILURE);
        }
        data[i] = i;
    }
    for (size_t i = 0; i < vsize(data); i++) {
        printf("%zu\n", data[i]);
    }
    vfree(data, NULL);
    return 0;
}

Вопрос в следующем: хорошо ли выровнен адрес результата malloc (void *) + размер struct для любого типа? Хорошо ли определено поведение этого кода?

Ответы [ 2 ]

7 голосов
/ 26 апреля 2019

Выровнен ли адрес malloc + size_t * 3 для любого типа?

Нет, не совсем точно.

Убедитесь в фундаментальном выравнивании делая префикс данных также фундаментальное выравнивание .

Одним из способов является использование union из struct vector и max_align_t.Размер union uvector будет кратен фундаментальному выравниванию .

max_align_t, которое является типом объекта, выравнивание которого настолько велико, насколько это поддерживается реализацией ввсе контексты;

union uvector {
  max_align_t a;
  struct vector {
    size_t capacity;
    size_t typesize;
    size_t size;
  } v;
};

union uvector *uvector;
unsigned char *data;

data = calloc(1, sizeof *uvector ...
...
return data + sizeof *uvector;

Ответ имеет некоторые недостатки - переработан в другом ответе.

2 голосов
/ 29 апреля 2019

Выровнен ли адрес malloc + size_t * 3 для любого типа?

Нет, поскольку могут быть стандартные типы, которые имеют более высокие требования к выравниванию, чем size_t.Требование выравнивания стандартного типа будет одним из числа фундаментальных выравниваний , используемых компилятором.

C11 <stddef.h> определяет стандартный тип max_align_t, выравнивание которого равнолюбой стандартный тип, поддерживаемый реализацией.(Могут быть расширенные типы, которые требуют большего выравнивания, чем max_align_t.) C11 также имеет ключевые слова _Alignof и _Alignas.

_Alignof - это оператор, который работает с типом и егорезультат - целочисленное константное выражение, равное требованию выравнивания этого типа в байтах.Наибольшее фундаментальное выравнивание задается _Alignof(max_align_t).

Ключевое слово _Alignas используется в спецификаторах выравнивания как часть объявления, чтобы увеличить требование выравниванияобъявленный объект или член.(Его нельзя использовать для уменьшения требования выравнивания объекта или элемента.) Спецификатор выравнивания имеет одну из следующих форм:

  • _Alignas ( имя типа )
  • _Alignas ( постоянное выражение )

где константа-выражение указывает требуемое выравнивание в байтах.Первая форма с имя типа эквивалентна _Alignas (_Alignof ( имя типа ) ).

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

struct vector {
    _Alignas(max_align_t)
    size_t capacity;
    size_t typesize;
    size_t size;
};

Посколькупервый элемент struct vector теперь имеет максимально возможное фундаментальное выравнивание, затем struct vector в целом имеет такое же требование выравнивания (если только оно не содержит нестандартных типов, для которых требуется расширенное выравнивание ).Если необходимо.компилятор добавляет дополнительное заполнение к концу struct vector, чтобы обеспечить кратность sizeof(struct vector) _Alignof(max_align_t).

...