2015-05-03 4 views
1

Краткая версия:Кастинг Структуры пустоты указателей на структуры с типизированными указателями

Пусть у меня есть два структур:

struct charPtrWithLen 
{ 
size_t len; 
char * charPtr; 
} 

struct voidPtrWithLen 
{ 
size_t len; 
void * voidPtr; 
} 

Есть ли способ бросить voidPtrWithLen в charPtrWithLen и наоборот, или даже лучше , неявно преобразовывать один в другой, почти так же, как char * и void * могут быть легко переведены и неявно преобразованы между собой?

Другими словами:

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

Длинная версия, с вовлеченной например:

Таким образом, пустота указатели удивительно гибкая, так что я могу это сделать:

int foo(void * ptr, size_t dataLen); 
/* ... */ 
char * c; 
size_t c_n; 
/* ... */ 
foo(c, c_n); 
/* ... */ 
int * i; 
size_t i_n; 
/* ... */ 
foo(i, i_n); 

Но поскольку картина «указатель на произвольный массив длины, а также размер там-то "настолько распространено, предположим, что в какой-то момент я устаю указывать свои различные функции в терминах пар аргументов, указателя и длины, и вместо этого я начинаю кодировать с такими парами, заключенными в структуру вместо:

typedef struct 
{ 
    size_t v_n; 
    void * v; 
} 
pointerWithSize; 
/* ... */ 
int foo(pointerWithSize); 

Пока все хорошо. Я всегда могу назначить «char * c» или «int * i» в «void * v» pointerWithSize с минимальными трудностями. Но когда вы делаете это достаточно долго, используя один и тот же шаблон, вы сталкиваетесь со следующей проблемой: достаточно скоро у вас есть куча общих функций, которые работают с данными агностически и, таким образом, счастливы принять недействительные указатели, например такие вещи, как:

pointerWithSize combinePointersWithSize(pointerWithSize p1, pointerWithSize p2); 
int readFromStream(FILE * readFromHere, pointerWithSize * readIntoHere); 

Но вы в конечном итоге с функциями, которые по своей сути предназначены для конкретных типов данных:

size_t countOccurancesOfChar(pointerWithSize str, char c); 
int summate(pointerWithSize integers); 

И тогда вы в конечном итоге с раздражением от того, чтобы сделать проливает внутри последней категории функций. Например. вы в итоге получаете такие вещи: /* Это внутри countOccurancesOfChar */ if ((char *) str.m) [i] == c) { /* ..или это внутри сумма: */ sum + = ((int *) integers.m) [i];

Итак, вы попадаете в точку, в которой у вас есть много функций, которые действуют конкретно на «строки с размером», и во всех этих случаях вы не хотите, чтобы вокруг с указателями void не было необходимости. Поэтому вместо того, в тех случаях, вы начинаете делать вещи, как это:

typedef struct 
{ 
    size_t v_n; 
    char * v; 
} 
stringWithSize; 
/* ... */ 
size_t countOccurancesOfChar(stringWithSize str, char c); 
int parseFormatting(stringWithSize str, struct someFormat_t foo); 

, который является большим, потому что теперь весь код, связанной строка не должен быть завален слепками. НО, теперь я не могу использовать свою замечательную общую функцию combPointersWithSize, чтобы объединить мои строки, содержащиеся в stringWithSize, таким образом, чтобы это было синтаксически чисто, как я мог, если бы я все еще писал свои функции в терминах двух отдельных аргументов для каждого указателя - и-размер.

Чтобы закончить рисунок:

pointerWithSize combinePointersWithSize(pointerWithSize p1, pointerWithSize p2); 
void * combineAlternative(void * p1, size_t p_n1, void * p2); 
/* ... */ 
stringWithSize a, b, c; 
/* ... */ 
/* This doesn't work, incompatible types: */ 
c = combinePointersWithSize(a, b); 
/* But this works, because char * can be passed into void * parameter. */ 
c.v_n = a.v_n + b.v_n; 
c.v = combineAlternative(a.v, a.v_n, b.v, b.v_n); /* Works fine. */ 

Возможные решения я Продуманные:

1: Не писать свои функции с теми структурами в качестве аргументов, вместо того, чтобы писать их с отдельными аргументами пары. Но это большая часть того, чего я хочу избежать в первую очередь - мне нравится «чистота» и ясность намерения, что имеет размер size_t и указатель, объединенный в одну структуру.

2: Есть ли что-то вроде этого:

stringWithSize a, b, c; 
/* ... */ 
pointerWithSize d; 
d = combinePointersWithSize((pointerWithSize){.v=a.v, .v_n=a.v_n}, (pointerWithSize){.v=b.v, .v_n=b.v_n}) 
/* and then do either this: */ 
c.v = d.v; 
c.v_n = d.v_n; 
foo(c); 
/* ..or this: */ 
foo((stringWithSize){.v=d.v, .v_n=d.v_n}); 

..но я думаю, что большинство согласится, что это также, как плохо или хуже исходной задачи литья в библиотечных функций. На первый взгляд это выглядит хуже, потому что он выгружает нагрузку на клиентский код вместо кода библиотеки, который, мы надеемся, будет довольно стабильным после его реализации/завершения (включая тестирование/etc). С другой стороны, если вы сохранили каждую функцию, определенную с точки зрения void *, содержащего pointerWithSize, вы могли бы заставить подобные приведения к тому, что вы делаете внутри своих собственных функций, в другом месте их кода и, что еще хуже, теряя преимущество компилятора, кричащего на вас, потому что теперь код несет все в пределах одной структуры pointerWithSize.

Я также обеспокоен тем, как многие компиляторы там есть возможность оптимизировать первую из двух вариантов этого решения прочь (где «d» серверы просто как владелец временного результата

3:. Союз . -of-указатели Вместо моего предыдущего примера pointerWithSize, я хотел бы сделать:.

typedef union 
{ 
    void * void; 
    char * char; 
    int * int; 
    /* ...and so on... */ 
} 
rainbowPointer; 

typedef struct 
{ 
    size_t v_n; 
    rainbowPointer v; 
} 
pointerWithSize; 

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

4: Я мог бы написать функции обертки для каждого типа перестановленного указателя. Я мог бы EVEN писать функциональные макросы, чтобы определить каждый из этих типов типов с указателем и размером, который в одном и том же мачете генерирует функции обертки. Например:

#define pointerWithSizeDef(T, name) \ 
typedef struct \ 
{ \ 
    size_t v_n; \ 
    T * v; 
} \ 
name; \ 
foo_ ## name (name p1) \ 
{ \ 
    /* generic function code defined in macro */ \ 
    /* Or something like this: */ \ 
    foo((pointerWithSize){.v=p1.v, .v_n=p1.v_n}); 
}; 
/* Then, stuff like this: */ 
pointerWithSizeDef(char, stringWithSize) 

Моя интуиция заключается в том, что рано или поздно этот метод станет громоздким.

5: Если есть механизм, без влияния на производительность, но это непривлекательный иначе, я мог бы написать мои общие функции, как функции, как макросы, которые, в свою очередь, вызывающими основную фактическую функцию:

int foo_actual(void * v, size_t v_n); 
#define foo(p) \ 
foo_actual(p.v, p.v_n); 

..or даже что-то вроде этого, чтобы заменить произнесения синтаксис:

#define castToPointerWithSize(p) \ 
((pointerWithSize){.v=p.v, .v_n=p.v_n}) 
/* ... */ 
stringWithSize a; 
foo(castToPointerWithSize(a)); 

Но эти примеры для возможного-решение- # 5 шоу, я не могу на самом деле придумать способ сделать это, что не будет быстро стать возможной проблемой (например, если кто-то захочет разместить вызов функции, который вернул указательWithSize вместо «p» в приведенных выше примерах - вы будете запускать эту функцию дважды, и это не будет очевидно из кода.

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

ответ

1

Во-первых, любой тип «фактического» литья не допускается на букву стандарта, потому что C makes no guarantee at all указывает, что все указатели имеют одинаковый формат. Приведение от некоторого произвольного типа указателя к указателю на пустоту разрешено включать преобразование представления (которое обращается вспять, когда вы возвращаете его для доступа к данным), включая, возможно, другой размер указателя или указатель, существующий в отдельное адресное пространство. Поэтому простая переинтерпретация битового шаблона для изменения типа указателя небезопасна; «бит-паттерн» не гарантированно означает что-либо в частности, а битовые шаблоны других типов не гарантируются каким-либо определенным образом. (Сколько систем на самом деле используют это, я понятия не имею.)

Поскольку явное преобразование между void* и другими типами должно существовать где-то, использование конверсии целого значения, вероятно, является самой безопасной идеей. Что вы можете сделать, это определить макрос, чтобы быстро и легко создавать «функции преобразования» для вас, например:

#define GEN_CAST(NAME, FROM_TYPE, TO_TYPE)  \ 
    static inline TO_TYPE NAME(FROM_TYPE from) { \ 
    return (TO_TYPE){ .v=p.v, .v_n=p.v_n }; \ 
    } 

GEN_CAST(s_to_v, stringWithSize, pointerWithSize) 
GEN_CAST(v_to_s, pointerWithSize, stringWithSize) 

... что вы можете использовать вместо оператора отлитого в выражениях:

stringWithSize a, b, c; 
pointerWithSize d; 
d = combinePointersWithSize(s_to_v(a), s_to_v(b)); 
foo(v_to_s(d)); 

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

-2

Вы должны иметь возможность отбрасывать один в другое, преобразовывая его в указатель, отбрасывая его на указатель другого типа и разыгрывая его. Это будет работать и наоборот.

struct charPtrWithLen 
{ 
size_t len; 
char * charPtr; 
}; 

struct voidPtrWithLen 
{ 
size_t len; 
void * voidPtr; 
}; 

int main() { 
    struct charPtrWithLen cpwl = {.len = 6, .charPtr = "Hello"}; 

    struct voidPtrWithLen vpwl = *(struct voidPtrWithLen *)&cpwl; 

    return 0; 
} 

Примечание. Это будет работать только в том случае, если структура структуры одинакова для обеих структур.

+0

Но только потому, что один является 'void *', а другой - 'char *'. Если другой указатель на какой-то другой тип, это не переносится. –