Вопрос на C: одиночная разыменование в пустоте ** указатель двойной косвенной - PullRequest
6 голосов
/ 07 декабря 2010

Я получил это сообщение:

expected 'void **' but argument is of type 'char **'

, когда я попытался скомпилировать что-то похожее на это:

void myfree( void **v )
{
    if( !v || !*v )
        return;

    free( *v );
    *v = NULL;

    return;
}



Я нашел то, что я считаю решениемпосле прочтения этого вопроса о переполнении стека:
Избегайте предупреждения о несовместимых указателях при работе с двойным косвенным обращением - Переполнение стека

Поэтому я настроился примерно так:

Это законный C?Я разыменую пустой указатель, не так ли?

Спасибо, ребята


РЕДАКТИРОВАТЬ 12/10/2010 16:45 EST:
Как было указано, free(NULL) безопасно и покрытопо стандарту C.Кроме того, как обсуждено ниже, мой пример выше не является допустимым C. См. Ответ caf, ответ Zack и мой собственный ответ.

Поэтому мне будет легче инициализировать любые указатели на be-malloc'dкак NULL, а затем - просто free () и NULL напрямую в коде:

free( pointer );
pointer = NULL;

Причина, по которой я проверял NULL в myfree (), как я сделал, была из-за моего опыта работы с fclose ().fclose(NULL) может зависать в зависимости от платформы (например, xpsp3 msvcrt.dll 7.0.2600.5512), и поэтому я предположил (ошибочно), что то же самое может произойти с free ().Я решил, а не загромождать свой код с помощью операторов if, которые я мог бы лучше реализовать в функции.

Спасибо всем за хорошее обсуждение

Ответы [ 4 ]

4 голосов
/ 07 декабря 2010

Нет, это , а не допустимый C, если только вы не передадите адрес объекта void * в myfree() (так что вы можете просто сохранить свое первоначальное определение).

Причина в том, что в вашем примере объект типа char * (объект, объявленный как test в main()) изменяется через lvalue типа void * (lvalue *v в myfree()),§6.5 стандарта C гласит:

7 Объект должен иметь свое сохраненное значение, доступное только через выражение lvalue одного из следующих типов:

— a type compatible with the effective type of the object,
— a qualified version of a type compatible with the effective type of 
the object,
— a type that is the signed or unsigned type corresponding to the effective
type of the object,
— a type that is the signed or unsigned type corresponding to a qualified
version of the effective type of the object,
— an aggregate or union type that includes one of the aforementioned
types among its members (including, recursively, a member of a subaggregate
or contained union), or
— a character type.

Поскольку void * и char * не являются совместимыми типами, это ограничение было нарушено.Условие совместимости двух типов указателей описано в §6.7.5.1:

Для совместимости двух типов указателей оба должны иметь одинаковую квалификацию, и оба должны быть указателями на совместимые типы.

Для достижения желаемого эффекта необходимо использовать макрос:

#define MYFREE(p) (free(p), (p) = NULL)

(Нет необходимости проверять NULL, поскольку free(NULL) допустимо. Обратите внимание, чтоэтот макрос оценивает p дважды).

2 голосов
/ 07 декабря 2010

Это совершенно законно, но может запутать других людей, которые читают ваш код.

Вы также можете использовать приведение для устранения предупреждения:

myfree((void **)&rest);

Это более читабельно и понятно.

1 голос
/ 11 декабря 2010

ответ кафе правильный: Нет, это не законно .И как Зак указывает, что нарушение закона таким образом, вероятно, наименее вероятно, вызовет проблемы.

Я нашел то, что кажется другим решением в списке часто задаваемых вопросов comp.lang.c · Вопрос 4.9 , в котором отмечается, что необходимо использовать промежуточное значение void.

#include <stdio.h>
#include <stdlib.h>

void myfree( void **v )
{
    if( !v )
        return;

    free( *v );
    *v = NULL;

    return;
}

int main( int argc, char *argv[] )
{
    double *num;

    if( ( num = malloc( sizeof( double ) ) ) )
    {
        printf( "before: %p\n", num );

        {
            void *temp = num;
            myfree( &temp );
            num = temp;
        }

        printf( "after: %p\n", num );
    }

    return 0;
}


1 голос
/ 07 декабря 2010

В C у вас нет выбора, кроме как ввести актерский состав где-то здесь. Я хотел бы использовать макрос, чтобы убедиться, что все сделано правильно на сайте вызова:

void
myfree_(void **ptr)
{
    if (!ptr || !*ptr) return;
    free(*ptr);
    *ptr = 0;
}
#define myfree(ptr) myfree_((void **)&(ptr))

[Вы можете фактически назвать и функцию, и макрос "myfree", благодаря правилам C-бесконечной макро-рекурсии! Но это будет сбивать людей с толку. В ответе на долгое обсуждение ниже caf я также оговорю, что выражение *ptr = 0 здесь изменяет объект неизвестного типа с помощью псевдонима void**, который является поведением, не определяемым во время выполнения - однако, мое информированное мнение таково, что оно не будет вызывают проблемы на практике, и это наименее плохой вариант, доступный в простом C; Мне кажется, что макрос caf, который оценивает свой аргумент дважды, гораздо реже (на мой взгляд) вызывает реальные проблемы.]

В C ++ вы можете использовать шаблонную функцию, которая лучше по трем причинам: она избавляет от необходимости брать адрес чего-либо на сайте вызовов, не нарушает корректность типа и вы получите ошибку во время компиляции вместо сбоя во время выполнения, если вы случайно передали не указатель на myfree.

template <typename T>
void
myfree(T*& ptr)
{
    free((void *)ptr);
    ptr = 0;
}

Но, конечно, в C ++ у вас есть еще лучшие варианты, такие как интеллектуальные указатели и классы контейнеров.

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

...