Как правильно распечатать адрес переменной, уже освобожденной из кучи? - PullRequest
0 голосов
/ 20 июня 2019

Я создаю утилиту журналирования, которая отслеживает выделения и освобождения внутри созданной мной библиотеки.

Моя программа не вылетала, но я все еще скептически отношусь к своему подходу.

void my_free(struct my_type *heap)
{
    if (!heap)
        logger("Fatal error: %s", "Null pointer parameter");

    // I leave this here on purpose for the application to crash
    // in case heap == NULL
    free(heap->buffer);

    void *address = heap;

    free(heap);

    logger("Heap at %p successfully deallocated", address);
    // since heap->buffer is always allocated/deallocated together with
    // heap I only need to track down the heap address
}
  • Есть ли какие-либо проблемы с этим?
  • Могу ли я сохранить адрес в цифровой форме?Как в целочисленном без знака?Какой тип по умолчанию?

Ответы [ 5 ]

4 голосов
/ 21 июня 2019

Лучшая практика для управления указателями на объекты, которые могут быть освобождены, - преобразовывать каждый указатель в uintptr_t, пока он еще действителен (указывает на объект до его освобождения), и использовать эти значения uintptr_t для печати и другие цели.

Согласно C 2018 6.2.4 2, «Значение указателя становится неопределенным, когда объект, на который он указывает (или только что прошедший), достигает конца своего времени жизни». Основная причина этого правила - разрешить реализации C в что байты в указателе не содержат адрес прямой памяти, но содержат информацию, которая используется для поиска информации адреса в различных структурах данных. Когда объект освобождается, хотя байты в указателе не изменяются, данные в этих структурах могут изменяться, и тогда попытка использовать указатель может завершиться неудачей различными способами. Примечательно, что попытка напечатать значение может быть неудачной, поскольку прежний адрес больше не доступен в структурах данных. Однако, поскольку это правило существует, даже менее экзотические реализации C могут использовать его для оптимизации, поэтому компилятор C может реализовать free(x); printf("%p", (void *) x); эквивалентно free(x); printf("%p", (void *) NULL);, например.

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

#include <inttypes.h>
#include <stdint.h>
...
uintptr_t ux = (uintptr_t) (void *) x;
free(x);
…
printf("%" PRIxPTR, ux);

Обратите внимание, что для строгого соответствия мы сначала преобразуем указатель в void *, а затем в uintptr_t. Это просто потому, что стандарт C явно не определяет поведение преобразования любого указателя в uintptr_t, но действительно определяет поведение преобразования указателя на объект в void * и преобразования void * в uintptr_t.

1 голос
/ 21 июня 2019

Per стандарт C :

Спецификаторы преобразования и их значения:

...

р

Аргумент должен быть указателем на void. Значение указателя преобразуется в последовательность печатных символов способом, определяемым реализацией.

Итак, чтобы полностью соответствовать стандарту C, вам нужно привести heap к void *:

logger("Heap at %p successfully deallocated", ( void * ) address);

Тот факт, что вы использовали free() для указателя, не означает, что вы не можете напечатать значение указателя в большинстве реализаций (см. Комментарии к другим ответам, почему это может быть правдой) - это просто означает, что вы можете ' разыменовать указатель.

1 голос
/ 21 июня 2019

Ваш код в порядке.

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

Очевидно, что если вы попытаетесь что-то сделать с *heap или heap[i] после того, как память была освобождена, вы столкнетесь с неопределенным поведением, и все может произойти, но вы можете проверить или распечатать heap (или address) все, что вы хотите без проблем.


Где «действительный» означает «указывающий на объект в течение времени жизни этого объекта».
0 голосов
/ 21 июня 2019

Из-за моего ограниченного рейтинга я пока не могу комментировать посты других, поэтому использую «ответ», пока это не ответ, но комментарий.

Ответ / комментарий Эрика Постпишила, представленный выше, требует рецензирования. Я не согласен или не понимаю его рассуждения.

" ... Когда объект освобождается, хотя байты в указателе не меняются, данные в этих структурах могут изменяться, и тогда попытка использовать указатель может завершиться неудачей различными способами. В частности, попытка распечатать значение может не получиться, потому что .."

Обратите внимание на прототип стандартного вызова C free (): void free (void * ptr). В месте вызова значение ptr останется неизменным до и после бесплатного вызова - оно передается значением значение .

«Использовать» указатель будет / может произойти сбой различными способами, если под использованием мы подразумеваем deference после вызова free (). Печать значения указателя не вызывает его. Кроме того, печать (значение) указателя с вызовом printf с использованием% p должна выполняться, даже если представление значения указателя определено реализацией.

"* ... Однако, поскольку это правило существует, даже менее экзотические реализации C могут использовать его для оптимизации, поэтому компилятор C может реализовать free (x); printf ("% p ", (void *) x); эквивалентно free (x); printf ("% p", (void ) NULL) ;, например. ..."

Даже если он (компилятор) сделает это, я не могу легко увидеть, что бы это оптимизировало, вызов специального свернутого printf со значением 0 для указателя? Также рассмотрим что-то вроде:

extern void my_free (void * ptr); // определено где-то .. my_free (myptr); printf ("% p", myptr);

Было бы нечего оптимизировать, как вы предлагаете здесь, и он (компилятор) ничего не может сделать о значении myptr.

Так что нет, я не могу согласиться с вашей интерпретацией стандарта C на данном этапе.

(мой) EDIT:

Если только не некоторые "экзотические" реализации с указателем памяти, реализованным в виде структуры, и он приводится к / от "user" void * .. Тогда значение кучи пользователя будет недействительным или нулевым при попытке его печати .. Но тогда каждый вызов C, который принимает void * в качестве указателя на память, должен был бы различать, какой void * указывает на кучу, которая не - скажем, memcpy, memset, - и тогда это становится довольно занятым ...

0 голосов
/ 20 июня 2019

void *address = &heap; - >> void *address = heap;

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

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