В чем разница между инициализацией структуры в качестве указателя или нет? - PullRequest
1 голос
/ 25 февраля 2010

У меня есть следующее для моей структуры 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, где это уместно, при обработке динамических строк, иначе я бы получил много ошибок сегментации. Но этот код не дает мне никаких ошибок сегментации, и я не понимаю, почему и если я должен сделать это так.

Ответы [ 5 ]

2 голосов
/ 25 февраля 2010

Сначала оба решения совершенно верны!

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

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

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

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

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

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

2 голосов
/ 25 февраля 2010

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

2 голосов
/ 25 февраля 2010

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

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

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

1 голос
/ 25 февраля 2010

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

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

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

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

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

0 голосов
/ 25 февраля 2010

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

...