LINUX.ORG.RU

Размер структуры с вложенным массивом структур

 ,


0

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

#define MAX_DATA 512

struct Child {
    int id;
    int set;
    char name[MAX_DATA];
    char email[MAX_DATA];
};

struct Database {
    int max_rows;
    int max_data;
    struct Child *rows;
};

struct Child *
Allocate_childs(int max_rows)
{
    struct Child *rows = malloc(max_rows * sizeof(struct Child));
    assert(rows != NULL);
    return rows;
}

struct Database *
Allocate_database(int max_rows)
{
    struct Database *db = malloc(max_rows * sizeof(struct Child) + sizeof(struct Database));
    assert(db != NULL);
    return db;
}

int main(int argc, char *argv[])
{
    int max_rows = 100;

    if (argc > 1) {
        max_rows = atoi(argv[1]);
    }

    struct Child *rows = Allocate_childs(max_rows);
    struct Database *db = Allocate_database(max_rows);
    db->rows = rows;
    db->max_rows = max_rows;

    printf("sizeof rows: %lu\n", sizeof(*rows));
    printf("sizeof db: %lu\n", sizeof(*db));
    printf("sizeof db (correct?): %lu\n", sizeof(*db) + sizeof(*rows));

    return 0;
}

Правильно ли я понял, что sizeof() в коде выше выдает не то, что я желаю и надо считать ручками? Т.е. верно считать sizeof(*db) + sizeof(*rows). Или может я что-то не понял? Нужно создавать структуры с динамическим массивом другой структуры. Погуглил, но ответа точного не нашел. Про VLA знаю, но пока хочу сделать все по-старинке.

★★★★★

Последнее исправление: gh0stwizard (всего исправлений: 2)

Ответ на: комментарий от AptGet

Так, упростил все до такого и всеравно не понимаю, почему sizeof() выводит не то, что я ожидаю:

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

#define MAX_DATA 512

struct Child {
    int id;
    int set;
    char name[MAX_DATA];
    char email[MAX_DATA];
};

void
Allocate_childs(int max_rows)
{
    struct Child **rows = malloc(max_rows * sizeof(struct Child *));
    int i = 0;

    for (i = 0; i < max_rows; ++i) {
        rows[i] = malloc(sizeof(struct Child));
    }

    printf("Allocate_childs: %lu [%d]\n", sizeof(**rows), max_rows);
    // Allocate_childs: 1032 [100]
}

int main(int argc, char *argv[])
{
    int max_rows = 100;

    if (argc > 1) {
        max_rows = atoi(argv[1]);
    }

    Allocate_childs(max_rows);

    return 0;
}

gh0stwizard ★★★★★
() автор топика

если тебе надо памяти для одного объекта Database:

malloc(sizeof(struct Database))

sizeof выдает то, что и должен, а ты хочешь, чтобы он выдавал это?

sizeof(*db->rows) * db->max_rows;

выше тебе уже дали подсказку

Ower
()

А что надо-то? sizof структуры будет равен сумме размеров всех элементов структуры + выравнивание. Т.е. ожидаемо, что sizeof(struct Child) составит 4+4+4+4 = 16 байт на 32-битной архитектуре и где-то так 24 байта на 64-битной.

Anon
()
Ответ на: комментарий от Ower

Вот аналогичный вопрос: http://stackoverflow.com/questions/12334343/malloc-an-array-of-struct-pointer...

Только на код ниже мой gcc ругается initialization from incompatible pointer type[/inlie]:

struct mystruct **ptr = (struct test *)malloc(n*sizeof(struct test *));
По идее sizeof(**ptr) должен вернуть n * sizeof(struct test *);, т.е. n * 8. Но у меня почему-то gcc считает, что размер равен размеру структуры, для struct Child это 1032 байт. И вот я сижу и недоумеваю, что получаю в итоге. Нужен всего лишь массив на ссылки со структурами или массив структур (первое, по идее, предпочтительней).

gh0stwizard ★★★★★
() автор топика
Ответ на: комментарий от Anon

А что надо-то?

Не получается получить размер массива со ссылками на структуры равный n * 8. Получаю ерунду =\

gh0stwizard ★★★★★
() автор топика
Ответ на: комментарий от Anon
#include <stdio.h>
#include <stdlib.h>

#define MAX_DATA 512

struct Child {
    int id;
    int set;
    char name[MAX_DATA];
    char email[MAX_DATA];
};

int main(void)
{
    int max_rows = 100;

    // gcc warning: initialization from incompatible pointer type
    struct Child **rows = (struct Child *)malloc(max_rows * sizeof(struct Child *));
    //struct Child **rows = malloc(max_rows * sizeof(struct Child *));

    printf("**rows: %lu [%d]\n", sizeof(**rows), max_rows);
    printf("*rows: %lu\n", sizeof(*rows));
    printf("rows: %lu\n", sizeof(rows));

    return 0;
}

Вывод:

**rows: 1032 [100]
*rows: 8
rows: 8
Почему 1032, а не 800?

gh0stwizard ★★★★★
() автор топика
Ответ на: комментарий от invy

Это логично, и мне понятно. Т.е. верно ли, что sizeof() никогда не считает размеры вложенных данных/структур? Даже если я их зааллокейтил после.

gh0stwizard ★★★★★
() автор топика
Ответ на: комментарий от gh0stwizard

Почему 1032, а не 800?

ответ в первом комментарии

с помощью sizeof ты не сможешь узнать сколько памяти ты динамически выделил

Ower
()
Ответ на: комментарий от gh0stwizard

По идее sizeof(**ptr) должен вернуть n * sizeof(struct test *);, т.е. n * 8.

Нет, не должен. sizeof не так работает.

sizeof(**ptr) == sizeof(struct mystruct)

AptGet ★★★
()

Кажется разобрался. Всем спасибо.

gh0stwizard ★★★★★
() автор топика
Ответ на: комментарий от gh0stwizard

считает, если там непосредственно структура или массив(но не указатель)

struct Database {
    int max_rows;         // sizeof(int)
    int max_data;         // sizeof(int)
    struct Child rows;    // sizoef(Child)
};

struct Database {
    int max_rows;         // sizeof(int)
    int max_data;         // sizeof(int)
    struct Child rows[ROWS_COUNT];   // sizoef(Child) * ROWS_COUNT
};

invy ★★★★★
()
Последнее исправление: invy (всего исправлений: 4)
Ответ на: комментарий от invy

Да, я как раз переделываю этот вариант :) Когда все задано - никаких проблем нет. Зато сейчас куча проблем возникает. Сейчас вот надо писать в файл это malloc'ированные структуры в файл и теперь вместо одного вызова fwrite() надо несколько раз ее вызывать =\

gh0stwizard ★★★★★
() автор топика
Ответ на: комментарий от gh0stwizard

Блин, сначала ответил, а потом в код посмотрел. У тебя массив в структуре статический → sizeof его считает. Итого на 64-битной архитектуре имеем: 8 байт на 2 int'а + 2 массива по 512 байт (с выравниванием вроде все ОК получается). Итого: 1024+8=1032.

struct Child { int id; int set; char name[MAX_DATA]; char email[MAX_DATA]; };

Anon
()
Ответ на: комментарий от gh0stwizard

Япона мать! Ну воткни ты в свою структуру еще параметр Len, или сделай обертку. В общем, чтобы знать, сколько там элементов в массивах.

Anon
()
Ответ на: комментарий от Anon

gcc выравнивает все по правой части, это я уже понял. Считает все правильно, просто не думал, что struct математика такая же как для массивов + malloc. Т.е. все дело в работе malloc.

gh0stwizard ★★★★★
() автор топика
Ответ на: комментарий от Anon

Ну воткни ты в свою структуру еще параметр Len, или сделай обертку.

Да воткнуто :) Прочитал, что struct — «first class citizen», в отличие от array[] и подумал, что Си все сделает за меня :)

gh0stwizard ★★★★★
() автор топика
Ответ на: комментарий от gh0stwizard

Так, упростил все до такого и всеравно не понимаю, почему sizeof() выводит не то, что я ожидаю:

можно для нетелепатов: чего ты ожидаешь-то?

emulek
()
Ответ на: комментарий от gh0stwizard

Не получается получить размер массива со ссылками на структуры равный n * 8. Получаю ерунду =\

научись в одном сообщении совмещать вопрос и код. У меня тут 11' экранчик, я 10 окон открыть не могу, а скролить лень. Проще тебя куда-нить послать.

emulek
()
Ответ на: комментарий от gh0stwizard

не думал, что struct математика такая же как для массивов + malloc. Т.е. все дело в работе malloc.

вообще-то malloc никакого отношения к sizeof НЕ имеет. Ибо первая считается в рантайме, а второй при компиляции.

emulek
()
Ответ на: комментарий от gh0stwizard

Не надо переделывать, зачем. Храни кол. во строк в структуре.
Добавил - инкремент, удалил - декремент. Пиешь в файл - кол.во сток тоже пишется, считываешь - читаешь кол.во, выделяешь память, заполняешь.

invy ★★★★★
()

Давай по порядку. Структура (как и массив) это непрерывный битовый паттерн в памяти. Ее размер складывается из размеров элементов, с поправкой на выравнивание. sizeof возвращает именно это. А вообще sizeof вычисляется в момент компиляции, по ссылкам не ходит, ибо Ъ, и относится только к спецификатору типа, а никак не к его содержимому, которое только у тебя в голове. Помнишь в прошлый раз sizeof(char a[10]) == 10, а sizeof(char *ap) == 4? Здесь то же самое.

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

struct Database {
    size_t rows_size; // что было передано в malloc/realloc
    size_t nrows; // сколько там реально использовано
    struct Child *rows;
};

struct Database *
make_database(void)
{
    struct Database *db = malloc(sizeof(*db));
    assert(db);
    db->nrows = 0;
    db->rows = сalloc(db->rows_size = 256, sizeof(db->rows[0]));
    return db;
}

void
grow_database(struct Database *db, size_t nrows)
{
    size_t old_size = db->rows_size;
    db->rows = realloc(db->rows, db->rows_size += nrows);
    assert(db->rows);
    memset(&db->rows[old_size], 0, sizeof(db->rows[0]) * nrows);
}

size_t
total_database_footprint(struct Database *db)
{
    return sizeof(*db) + sizeof(db->rows[0]) * db->rows_size;
}
arturpub ★★
()
Ответ на: комментарий от invy

sizeof(VLA одиночный), т.е. массив на стеке, считается в рантайме. sizeof(VLA как последний член структуры) всегда == 0.

arturpub ★★
()
Ответ на: комментарий от gh0stwizard

Нет. rows это указатель на struct Child (или на первый элемент массива из них). Ты наверное путаешь со struct Child rows[1] + выделение памяти для них прямо в чанке базы.

struct Child *rows — указатель на первого Child, struct Child **rows — указатель на указатель на первого Child (смысл этой конструкции тяжело подобрать к твоей задаче).

arturpub ★★
()
Ответ на: комментарий от gh0stwizard

malloc и sizeof ортоганальны друг другу.

anonymous
()
Ответ на: комментарий от arturpub

struct Child *rows — указатель на первого Child

А как тогда записать в твоем случае структуру db и все что в ней в файл?

fwrite(db, sizeof(*db), 1, fp);

// дальше это??
fwrite(db->rows, sizeof(*db->rows[0]), db->rows_size, fp);

gh0stwizard ★★★★★
() автор топика
Ответ на: комментарий от gh0stwizard

Я кажется понял источник конфузии :) Просто помни о том, что rows == &rows[0] и это адрес первого элемента непрерывного массива. Смотри, блока памяти (по-сишному: объекта) здесь всего два — база и массив строк. То есть у тебя два объекта с типами struct Database и struct Child[n] (без звездочек, это же конечные объекты). На базу мы ссылаемся как struct Database *, так? В базе есть ссылка на массив строк. А как мы [обычно] ссылаемся на массив <type> m[n]? <type> *mp же! Значит struct Child *rows.

arturpub ★★
()
Ответ на: комментарий от arturpub

Кажись понял. Ну твой вариант это следующий этап :) Ладно, задумку вроде понял. Писать все же придется по циклу (после grow_database()):

for (i = 0; i < db->nrows; i++) {
  fwrite(db->rows[i], sizeof(db->rows[0]), 1, fp);
}
Верно?

gh0stwizard ★★★★★
() автор топика
Ответ на: комментарий от emulek

++

От них только вред: не углядишь где-нибудь, а в один прекрасный момент — бац! И переполнился стек...

Anon
()
Ответ на: комментарий от gh0stwizard
fwrite(db, sizeof(*db), 1, fp);
fwrite(db->rows, sizeof(db->rows[0]), db->rows_size, fp);

Без звездочки, потому что db->rows[0] это уже объект типа struct Child.

Но вообще сериализация так не делается, выкинь все указатели, при обратном поднятии с диска они уже будут не актуальны. И для портабельности надо либо мутить с htons/ntohs/htonl/ntohl, либо писать по-человечески.

void
dump_database(struct Database *db, FILE *f)
{
    fprintf(f, "nrows %d\n", db->nrows);
    for (size_t i = 0; i < db->nrows; i++) {
        fprintf(f, "id %d\n", db->rows[i].id);
        fprintf(f, "set %d\n", db->rows[i].set);
        fprintf(f, "name %s\n", &db->rows[i].name); // unterminated strings are bad, mkay?
        fprintf(f, "email %s\n", &db->rows[i].email);
    }
}

Обратно собирать scanf'ом.

arturpub ★★
()
Ответ на: комментарий от arturpub

fwrite(db->rows, sizeof(db->rows[0]), db->rows_size, fp);

Valgrind на это ругается. Даже несмотря на то, что calloc() все зануляет. Но файл так пишется верного размера. В тоже время, если делать через struct Child **rows + malloc/calloc на каждый db->rows[i], то все пишется как надо.

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

Да, опосля так и сделаю. Пока мучаюсь с тем, что знаю.

gh0stwizard ★★★★★
() автор топика
Последнее исправление: gh0stwizard (всего исправлений: 1)
Ответ на: комментарий от gh0stwizard

Пока мучаюсь с тем, что знаю.

лучше сразу делать нормально. Есть только один переносимый формат — текстовый. Если боишься размера, то

1. преждевременная оптимизация

2. не намного больше

3. man zlib, и будет меньше, чем бинарный велосипед.

emulek
()
Ответ на: комментарий от gh0stwizard

А зачем тебе создавать (массив) указатель(ей) на (массив) struct Child?

Вопрос по твоей ссылке здесь не к месту.

anonymous
()
Ответ на: комментарий от gh0stwizard

Починил:

void
read_database(struct Database *db, const char *filename)
{
    FILE *fp;
    int rc = 0;

    free(db->rows);

    fp = fopen(filename, "r+b");
    assert(fp);

    rc = fread(db, sizeof(*db), 1, fp);
    assert(rc == 1);

    // refresh
    db->rows = calloc(db->rows_size, sizeof(db->rows[0]));

    rc = fread(db->rows, sizeof(db->rows[0]), db->rows_size, fp);
    assert(rc == db->rows_size);

    rc = fclose(fp);
    assert(rc != EOF);
}
Ладно, пойду дальше читать, почему fread как-то странно работает :)

gh0stwizard ★★★★★
() автор топика
Ответ на: комментарий от gh0stwizard

Ну ты споткнулся как раз об чо я говорил — положил на диск указатель, потом close_database его прибил, потом make_database создал свои rows, но ты их fread'ом затер (они кстати утекли), и теперь rows это старый указатель, который теперь dangling, и ты туда читаешь fread'ом.

Refresh не спасет, надо либо read_database делать конструктором, либо в нем существующую базу читать до текущего предела (rows_size), а потом делать grow. В обоих случаях на указатель не смотреть.

struct Database *
read_database(FILE *f)
{
    struct Database t, *db;
    fread(&t, sizeof(t), 1, f);
    db = malloc(sizeof(*db));
    db->rows = malloc(db->rows_size = t.rows_size);
    db->nrows = t.nrows; // смотря как хранить
    fread(db->rows, sizeof(db->rows[0]), db->rows_size, f);
    return db;
}

void
read_database_inplace(struct Database *db, FILE *f)
{
    struct Database t;
    fread(&t, sizeof(t), 1, f);
    if (db->rows_size < t.rows_size)
        grow_database(db, t.rows_size - db->rows_size);
    db->nrows = t.nrows; // смотря как хранить
    fread(db->rows, sizeof(db->rows[0]), db->rows_size, f);
}

Как-то так.

arturpub ★★
()
Ответ на: комментарий от arturpub

Временные структуры ненужны, имхо. После fread(&t, sizeof(t), 1, f); t.rows_size указывает на размер и исходя из него сделать для t.rows malloc()/calloc(), потом в них вторых fread() записать данные. Собственно что я и сделал. Вопрос коррупции данных вообще здесь не стоит. Конечно, если делать все грамотно, то надо дофига заморачиваться.

gh0stwizard ★★★★★
() автор топика
Ответ на: комментарий от arturpub

Если не хранить пустую преаллоцированную область (нафиг она тебе на диске), то дамп нужно делать только до nrows, а не на все rows_size, и чтение соответственно:

    db->rows = malloc(db->rows_size = t.nrows);

arturpub ★★
()
Ответ на: комментарий от arturpub

дамп нужно делать только до nrows, а не на все rows_size

Да это я прекрасно понимаю. В задании там такое не требуется. Можно дофига всего наоптимизировать и там еще другие задачи имеются :)) Я принцип понял, еще успею написать более качественный код.

gh0stwizard ★★★★★
() автор топика
Ответ на: комментарий от gh0stwizard

А, ну согласен, просто вместо конструктора инициализатор получился, и старую память надо перед calloc освободить.

дофига заморачиваться

Да нет, вроде все уже что можно учли :)

arturpub ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.