В чем разница между инициализацией структуры как указателя или нет?

У меня есть следующая структура моей HashTable:

typedef char *HashKey;
typedef int HashValue;

typedef struct sHashElement {
    HashKey key;
    HashValue value;
} HashElement;

typedef struct sHashTable {
    HashElement *items;
    float loadFactor;
} HashTable;

Я никогда не думал об этом до сих пор, но я только что понял, что есть два способа, как я могу это использовать:

Вариант 1:

void hashInitialize(HashTable *table, int tabSize) {
    table->items = malloc(sizeof(HashElement) * tabSize);

    if(!table->items) {
        perror("malloc");
        exit(1);
    }

    table->items[0].key = "AAA";
    table->items[0].value = 45;
    table->items[1].key = "BBB";
    table->items[1].value = 82;

    table->loadFactor = (float)2 / tabSize;
}


int main(void) {
    HashTable t1;
    int i;

    hashInitialize(&t1, HASHSIZE);

    for(i = 0; i < HASHSIZE - 1; i++) {
        printf("PAIR(%d): %s, %d\n", i+1, t1.items[i].key, t1.items[i].value);
    }

    printf("LOAD FACTOR: %.2f\n", t1.loadFactor);

    return 0;
}

Вариант 2:

void hashInitialize(HashTable **table, int tabSize) {
    *table = malloc(sizeof(HashTable));

    if(!*table) {
        perror("malloc");
        exit(1);
    }

    (*table)->items = malloc(sizeof(HashElement) * tabSize);

    if(!(*table)->items) {
        perror("malloc");
        exit(1);
    }

    (*table)->items[0].key = "AAA";
    (*table)->items[0].value = 45;
    (*table)->items[1].key = "BBB";
    (*table)->items[1].value = 82;

    (*table)->loadFactor = (float)2 / tabSize;
}


int main(void) {
    HashTable *t1 = NULL;
    int i;

    hashInitialize(&t1, HASHSIZE);

    for(i = 0; i < HASHSIZE - 1; i++) {
        printf("PAIR(%d): %s, %d\n", i+1, t1->items[i].key, t1->items[i].value);
    }

    printf("LOAD FACTOR: %.2f\n", t1->loadFactor);

    return 0;
}

Вопрос 1. Обе они дают одинаковый результат. В main оба примера выводят правильную пару ключ/значение. Итак, чем же они отличаются, кроме изменения синтаксиса (использование (*table) вместо просто table), дополнительного кода для выделения памяти для структуры HashTable и объявления указателя HashTable?

В последнее время я писал несколько структур данных, таких как стеки, связанные списки, двоичные деревья поиска и теперь хеш-таблицы. И для всех них я всегда использовал альтернативу 2. Но теперь я думаю, мог ли я использовать альтернативу 1 и упростить код, удалив большую часть * и &, которые повсюду.

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

Вопрос 2: Как видно из кода структуры, HashKey — это указатель. Однако я не использую strdup и malloc для выделения места для этой строки. Как и почему это работает? Это нормально делать? Я всегда использовал malloc или strdup, где это уместно, при обработке динамических строк, иначе я получил бы много ошибок сегментации. Но этот код не дает мне никаких ошибок сегментации, и я не понимаю, почему и должен ли я делать это так.


person rfgamaral    schedule 25.02.2010    source источник


Ответы (5)


Во-первых, оба решения совершенно правильные!

Альтернатива 1:

Ваша HashTable объявлена ​​в main, что означает, что структура находится где-то в стеке вызовов. Структура будет уничтожена, если вы покинете область видимости. Примечание. В вашем случае этого не может произойти, потому что объявление находится в основном, поэтому область действия заканчивается при выходе из процесса.

Альтернатива 2:

У вас есть HashTable* (указатель) в стеке вызовов, поэтому вам нужно выделить память для структуры. Для этого вы используете malloc.

В обоих случаях ваша структура правильно распределена. Основное отличие будет в выступлениях. Гораздо эффективнее выделять в стеке, но вы не можете выполнять динамическое выделение. Для этого вам нужно использовать malloc. Итак, иногда вам приходится использовать malloc, но старайтесь избегать большого количества malloc, если вы хотите создать высокопроизводительное приложение.

Это достаточно ясно? :)

person Nicolas Guillaume    schedule 25.02.2010

В варианте 1 вызывающая сторона выделяла бы table, но ваша функция выделяла бы его содержимое, что не всегда является хорошей идеей с точки зрения управления памятью. Альтернатива 2 сохраняет все распределения в одном и том же месте.

person Ofir    schedule 25.02.2010
comment
Кстати - в обоих примерах память не освобождается - person Ofir; 25.02.2010
comment
Я знаю, что память не освобождена, я все еще в процессе ее создания, это не окончательный код. - person rfgamaral; 25.02.2010

Как уже было сказано ранее, разница между двумя альтернативами заключается в управлении памятью. В варианте 1 вы ожидаете, что вызывающая сторона выделит память для таблицы до вызова; тогда как в альтернативе 2 требуется только объявление указателя, чтобы дать вам место для размещения памяти после ее создания.

На вопрос 2 простой ответ заключается в том, что вы присваиваете константу строке. Согласно следующему сайту, назначение настраивается во время компиляции, а не во время выполнения.

http://publications.gbdirect.co.uk/c_book/chapter6/initialization.html

person Bayou Bob    schedule 25.02.2010

для вопроса 2: (*table)->items[0].key = "AAA";

фактически помещает «AAA» в части памяти, доступные только для чтения, и char *key указывает на него, содержимое, указанное ключом, не может быть изменено.

(*table)->items[0].key[0]='a' дает и ошибка

Здесь вы можете найти дальнейшее обсуждение этого.

В чем разница между char s[ ] и char *s?

person Rozuur    schedule 25.02.2010

Единственная разница заключается в том, откуда берется память — локальные переменные обычно находятся в стеке, тогда как mallocs обычно берутся из кучи.

person David Pfeffer    schedule 25.02.2010
comment
Не совсем правильно. Локальные переменные не malloc ed. Память для них берется из The Stack. - person Vilx-; 25.02.2010