#c #memory
#c #память
Вопрос:
Я относительно новичок в программировании на C и в настоящее время работаю над простой хэш-таблицей (у меня есть опыт работы с другими языками).
Но теперь я столкнулся со странной проблемой памяти, и псевдо-решение, которое я нашел, еще более странное. Итак, у меня есть следующий код, который настраивает структуру моей хэш-таблицы:
typedef struct HashTable {
unsigned int size;
TableEntry *table[];
} HashTable;
HashTable *create_table(unsigned int size) {
HashTable *table = malloc(sizeof(unsigned int) size*sizeof(TableEntry *));
memset(table sizeof(unsigned int), 0, size*sizeof(TableEntry *));
table->size = size;
return table;
}
Я запустил этот код на двух машинах. На моем хост-компьютере с Windows это работает полностью нормально.
Но моя виртуальная машина Linux мне явно не нравится, потому что после вызова memset любой вызов malloc выдаст ошибку утверждения
server: malloc.c:2401: sysmalloc: Assertion (old_top == initial_top (av) amp;amp; old_size == 0) || ((unsigned long) (old_size) >= MINSIZE amp;amp; prev_inuse (old_top) amp;amp; ((unsigned long) old_end amp; (pagesize - 1)) == 0) failed.
Я уже трачу часы, пытаясь выяснить, что вызывает здесь ошибку. Я полагаю, что это, вероятно, имеет какое-то отношение к вызову memset, но пока это мне не помогло. Я также использовал Valgrind, но это тоже не дало мне никакой новой информации.
Теперь начинается та часть, которая смущает меня больше всего. Второй malloc между вызовом malloc и memset каким-то образом устраняет проблему (пример приведен ниже). Даже если я выделяю 0 байт, он все равно работает нормально.
Итак, мое «исправление» выглядит примерно так:
HashTable *create_table(unsigned int size) {
HashTable *table = malloc(sizeof(unsigned int) size*sizeof(TableEntry *));
void *test = malloc(0); //this line
memset(table sizeof(unsigned int), 0, size*sizeof(TableEntry *));
table->size = size;
return table;
}
Теперь я был бы очень, очень благодарен, если бы кто-нибудь мог помочь мне выяснить
- что вызывает ошибку
- с какой стати второй вызов malloc исправляет это? Возможно, просто какие-то странные вещи на C, но мне все еще любопытно
Комментарии:
1. Как правило, когда что-то волшебное «исправляет» проблему, это вызвано неопределенным поведением ранее в вашем коде. Перемещение местоположений в текстовом или сегменте данных может избежать симптома, который вы видели, но основная проблема все еще существует.
2.
sizeof(unsigned int)
должно бытьsizeof(HashTable)
, илиoffsetof(HashTable, table)
. Это позволяет выполнять заполнение между двумя элементами3. использование
calloc
вместоmalloc
упростит код и позволит избежать проблемы
Ответ №1:
«Какой-то чувак-программист» указал на ошибку, но есть более безопасный и более идиоматичный способ написать это.
Ваш подход по своей сути неверен, поскольку он предполагает, что элемент table
массива HashTable
начнется сразу после size
элемента. Но это не обязательно верно; компилятор может вставить дополнение между ними. Действительно, в 64-разрядных системах это обычно имеет место, потому unsigned int
что, вероятно, будет 4 байта, но ожидается, что указатели будут выровнены по 8-байтовой границе. Ваш текущий код также необходимо будет обновлять каждый раз, когда вы решите добавить или удалить участников из HashTable
.
Таким образом, лучший подход заключается в следующем, используя тот факт, что sizeof
применение к struct
типу с гибким элементом массива вернет размер всего, что предшествует FAM, включая любое заполнение:
HashTable *create_table(unsigned int size) {
HashTable *table = malloc(sizeof(HashTable) size*sizeof(TableEntry *));
memset(table->table, 0, size*sizeof(TableEntry *));
table->size = size;
return table;
}
Пара других возможных улучшений:
- при использовании
sizeof
многие люди предпочитают «разыменовывать» указатель, указывающий на нужный объект, а не называть его тип, поскольку это потенциально менее подвержено ошибкам. - Размеры или длины массивов обычно следует использовать
size_t
вместоunsigned int
. В 64-разрядной системе ваш текущий код не сможет обрабатывать хэш-таблицы размером более 4 ГБ, хотя система может поддерживать такие большие объекты просто отлично. - Неудобно использовать имя
table
как для вашей локальной переменнойcreate_table
, так и для членаHashTable
. - Вы должны проверить, что
malloc
это удалось, прежде чем пытаться инициализировать выделенную память.
Я мог бы написать:
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
typedef struct HashTable {
size_t size;
TableEntry *entries[];
} HashTable;
HashTable *create_table(size_t size) {
HashTable *table = malloc(sizeof(*table) size*sizeof(table->entries[0]));
if (!table) {
// abort or return NULL
}
memset(table->entries, 0, size*sizeof(table->entries[0]));
table->size = size;
return table;
}
Оставлено в качестве упражнения: правильно обработайте случай, когда параметр size
настолько велик, что sizeof(*table) size*sizeof(table->entries[0])
переполняется size_t
.
Комментарии:
1. Большое спасибо за вашу помощь! Чтобы обнаружить переполнение, могу ли я просто написать что-то вроде:
if((SIZE_MAX-sizeof(*table)) / sizeof(table->entries[0])) <= size)
? Или такой подход будет считаться неэлегантным / неправильным в C?2. @mizanshu: Да, это хороший способ сделать это.
Ответ №2:
Помните, что арифметика указателя выполняется в базовой единице указателя, это то, что приводит к, например p[i]
, точно так же, как *(p i)
(для любого допустимого указателя или массива p
и индекса i
).
Это означает, что ваше выражение table sizeof(unsigned int)
будет таким же, как amp;table[sizeof(unsigned int)]
, что, вероятно, не то, что вы ожидаете. Это, в свою очередь, приводит к тому, что ваш memset
вызов выводит запись за пределы и приводит к неопределенному поведению.
Чтобы быть правильным, вам нужно привести указатель table
к указателю char
, как в (char *) table sizeof(unsigned int)
.
Разница между ними заключается в добавленных смещениях байтов.
С помощью простого table sizeof(unsigned int)
(без приведения) вы добавляете sizeof(HashTable) * sizeof(unsigned int)
байты к указателю.
Когда вы приводите указатель, например, (char *) table sizeof(unsigned int)
вы добавляете sizeof(char) * sizeof(unsigned int)
байты к указателю.
Комментарии:
1. Вы определили проблему, но, как я отмечаю в своем ответе, я не думаю, что ваше решение действительно правильное.