Код падает, если я не помещу в него оператор printf - PullRequest
2 голосов
/ 15 апреля 2011

Это фрагмент кода из библиотеки массивов, которую я использую. Это нормально работает на Windows, но когда я компилирую с GCC на Linux, если происходит сбой в этой функции. пытаясь сузить проблему, я добавил к ней оператор printf, и код перестал падать.

void _arrayCreateSize( void ***array, int capacity )
{
    (*array) = malloc( (capacity * sizeof(int)) + sizeof(ArrayHeader) );
    ((ArrayHeader*)(*array))->size = 0;
    ((ArrayHeader*)(*array))->capacity = capacity;
    // printf("Test!\n");
    *(char**)array += sizeof(ArrayHeader);
}

Как только этот printf вынут, он снова падает на меня. Я полностью сбит с толку относительно того, почему это происходит.

Ответы [ 3 ]

5 голосов
/ 15 апреля 2011

Последняя строка в функции не выполняет то, что предполагалось.Код неясен до точки непроницаемости.

Похоже, что цель состоит в том, чтобы выделить массив int из-за sizeof(int) в первом выделении памяти.По крайней мере, если вы хотите выделить массив структурных указателей, вам нужно использовать sizeof(SomeType *), размер некоторого типа указателя (sizeof(void *) подойдет).Как написано, в 64-битной среде это приведет к ужасным ошибкам.

Массив выделяется с заголовком структуры (ArrayHeader), за которым следует собственно массив.Возвращаемое значение считается началом самого массива;ArrayHeader предположительно будет найден путем вычитания из указателя.Это безобразно, как грех, и не подлежит загрузке.Его можно заставить работать, но это требует особой осторожности и (как сказал Брайан Керниган) «если вы пишете код настолько умно, насколько это возможно, как вы собираетесь его отлаживать?».

К сожалению, последняя строка неверна:

void _arrayCreateSize( void ***array, int capacity )
{
    (*array) = malloc( (capacity * sizeof(int)) + sizeof(ArrayHeader) );
    ((ArrayHeader*)(*array))->size = 0;
    ((ArrayHeader*)(*array))->capacity = capacity;
    // printf("Test!\n");
    *(char**)array += sizeof(ArrayHeader);
}

Добавляет sizeof(ArrayHeader) * sizeof(char *) к адресу вместо предполагаемого sizeof(ArrayHeader) * sizeof(char).Поэтому в последней строке следует читать:

*(char *)array += sizeof(ArrayHeader);

или, как отмечено в комментариях и альтернативном ответе:

*(ArrayHeader *)array += 1;
*(ArrayHeader *)array++;

Попутно отмечу, что имя функции на самом деле не должно начинатьсяс подчеркиванием.Внешние имена, начинающиеся с подчеркивания, зарезервированы для реализации (компилятора C и библиотеки).


В связи с этим возникает вопрос: почему оператор printf() исправляет вещи?Ответ заключается в том, что это решает проблему.У вас есть Heisenbug, потому что есть злоупотребление выделенной памятью, и наличие printf() удается немного изменить поведение кода.

Рекомендация

  1. Выполнитьпрограмма под valgrind.Если у вас его нет, получите его.
  2. Пересмотрите код, чтобы функция проверяла возвращаемое значение из malloc() и возвращала указатель на структуру для выделенного массива.
  3. Используйте более понятный код, указанный в Michael Burr .
2 голосов
/ 15 апреля 2011

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

Несколько предложений:

  • Вы уверены, что вам нужно void ***?
  • , попробуйте заменить аргумент на malloc() с 10000.Это работает сейчас?

Более того, если вам нужны массивы, в которых хранятся некоторые метаданные, ваш текущий код - плохой подход.Чистое решение, вероятно, будет использовать структуру, подобную следующей:

struct Array {
    size_t nmemb;    // size of an array element
    size_t size;     // current size of array
    size_t capacity; // maximum size of array
    void *data;      // the array itself
};

Теперь вы можете передать объект типа Array в функции, которые знают о типе Array, и Array->data приведение кправильный тип для всего остального.Структура памяти может быть такой же, как в вашем текущем подходе, но доступ к метаданным значительно проще и особенно более очевиден.

Ваша основная аудитория - бедняга, который должен поддерживать ваш код через 5 лет.

1 голос
/ 15 апреля 2011

Теперь, когда Джонатан Леффлер указал, что это за ошибка , могу ли я предложить, чтобы функция была написана немного менее озадачивающе?:

void _arrayCreateSize( void ***array, int capacity )
{
    // aloocate a header followed by an appropriately sized array of pointers
    ArrayHeader* p = malloc( sizeof(ArrayHeader) + (capacity * sizeof(void*)));

    p->size = 0;
    p->capacity = capacity;

    *array = (void**)(p+1);   // return a pointer to just past the header 
                              //   (pointing at the array of pointers)
}

Смешайте в своем собственном желаемом случае сбоя malloc().

Я думаю, что это, вероятно, поможет следующему человеку, который должен смотреть на это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...