2017-01-03 14 views
2

У меня есть общий набор элементов, предоставляемых в виде библиотеки.C: Общая ошибка ADT при передаче указателей функций

/** Type for defining the set */ 
typedef struct Set_t *Set; 

/** Element data type for set container */ 
typedef void* SetElement; 

/** Type of function for copying an element of the set */ 
typedef SetElement(*copySetElements)(SetElement); 

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

Set setCreate(copySetElements copyElement); 

Я написал следующий тип и функция копирования:

typedef struct location_t { 
    char *name; 
} *Location; 

Location locationCopy(Location location){ 

    Location new_location = locationCreate(location->name); 

    return new_location; 
} 

* Очевидно, что я упростил все, чтобы сосредоточить обсуждение.

Когда я зову:

Set locations = setCreate(locationCopy); 

я получаю ошибки компилятора:

предупреждение: проходя аргумент 1 из 'setCreate' из несовместимых указатель типа

ожидается 'copySetElements', но аргумент имеет тип 'struct location_t * (*) (struct location_t *)'

+2

Не скрывайте природу указателя за 'typedef', за исключением, возможно, указателей на функцию. Это смущает всех, включая вас. –

ответ

3

Ваш пример может быть сводились к

typedef void * (*VF)(void *); 
typedef int * (*IF)(int *); 

IF a = 0; 
VF b = a; //Warning 

В тех C standard указателей на функции менее универсален, чем указатель на объекты.
Вы можете преобразовать указатель на объект в указатель на void (и обратно) без броска (и предупреждения), потому что есть пункт в стандарте, который явно позволяет это

Указатель к мочеиспусканию может быть преобразуется в указатель или из указателя на любой тип объекта.
Указатель на любой тип объекта может быть преобразован в указатель на void и обратно;
результат должен быть сравнить с исходным указателем.

Обратите внимание, что функция не является объектом.

Что касается указателей на функцию, единственное, что стандартные гарантии являются:

Указатель на функцию одного типа может быть преобразован в указатель на функцию другого типа и обратно; результат сравнивается с исходным указателем.
Если переделанный указатель используется для вызова функции, тип которого не совместим с ссылочного типа, поведение не определено

Позже стандарт уточняет, что это значит для двух функций, чтобы быть совместимым:

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

Теперь вы можете думать, что void* и struct location_t* совместимые типы, в конце концов вы можете назначить друг другу.
Но стандарт кристально ясно:

Два типа имеют совместимый тип, если их типы совпадают.

В дальнейшем это расширение распространяется на более сложные типы и квалифицированные типы.

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

Когда речь заходит о указателях разных типов, стандарт в основном касается назначения их взад и вперед.
Он не запрещает преобразование между несовместимыми типами указателей, но использование их - неопределенное поведение, поэтому предупреждение.


Чем лучше подход, чтобы выполнить бросок внутри функции, as suggested in this answer.

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

SetElement locationCopy(SetElement element) 
{ 
    Location location = (Location)element; 

    Location new_location = locationCreate(location->name); 

    return new_location; 
} 

Брикеты внутри функции снова подвержены непредсказуемому поведению, если SetElement указатели отлиты несовместимых указателей, но это не должно произойти, если вы будете называть locationCopy только с указателями на Location с (как вы на самом деле ожидать делать).


Как примечание стороны, некоторые программист может неодобрительно с помощью typedef, чтобы скрыть тип указателя.

+0

Очень хорошее использование цитаты для стандарта. Только примечание будет включать ссылку на конкретный стандарт, на который вы полагаетесь. (например, C89/90, C99, C11).Я не думаю, что это имеет значение в этом случае, но между ними есть тонкие и важные изменения. (Я вижу, что вы предоставили ссылку на проект С11 - этого достаточно) –

0

Вы определили copySetElements в

POINTER (SetElement => SetElement) 

Вы также определить setCreate, как

copySetElements => Set 

И вы называете setCreate с параметром locationCopy типа

POINTER(Location => Location) 

предупреждение генерируется из этого межправительственная морская консультативная организация совместимость объявленного типа параметра и тип фактического аргумента.