2013-10-03 7 views
13

У меня есть функция, которая принимает void** аргумент и целое число, которое указывает на его типДать обобщенную функцию в C, как обрабатывать строки

void foo (void** values, int datatype) 

Внутри функции, в зависимости от типа данных, я таНос это это путь:

if (datatype == 1) 
    *values = (int*) malloc (5 * sizeof(int)); 
else if (datatype == 2) 
    *values = (float*) malloc (5 * sizeof(float)); 

Все хорошо до сих пор. Однако, когда персональные строки попадают в картину, все становится сложнее. void** необходимо будет void***, так как мне нужно будет сделать что-то вроде этого:

*values = (char**) malloc (5 * sizeof(char*)); 
for(i=0;i<5;i++) 
    (*values)[i] = (char*) malloc (10); 
.. 
strncpy((*values)[0], "hello", 5); 

Как следует такая ситуация обрабатывается? Могу ли я передать char*** функции, ожидающей void**, но ввергните ее в нее?

void foo (void** values, int datatype) { 

if(datatype == 3) { 
    char*** tmp_vals = (char***) values; 
    *tmp_vals = (char**) malloc (5 * sizeof(char*)); 
    ... 
    (*tmp_vals)[i] = (char*) malloc (10 * sizeof(char)); 
    strncpy ( (*tmp_vals)[i], "hello", 5); 
} 

Так что я просто бросил void** в char***. Я пробовал это и игнорировал предупреждения, он работал нормально. Но разве это безопасно? Есть ли более изящная альтернатива?

+0

Где хранится содержимое струн? Простое выделение массива указателей не создает пространства для символов. Строки динамические и распределены в одном и том же месте, или статические, или произвольные миксы? –

+0

Эта же функция копирует данные во вновь созданный массив строк – jitihsk

+0

Но вы не создаете массив строк ... – zubergu

ответ

3

Вам не нужно (и, вероятно, не следует) использовать void ** - просто используйте обычный void *. Per C11 6.3.2.3.1 "указатель на void может быть преобразован в указатель или с любого указателя на любой тип объекта. Указатель на любой тип объекта может быть преобразован в указатель на void и обратно, результат сравнивается равным исходный указатель ". Переменная указателя, включая указатель на другой указатель, является объектом.void ** не является «указателем на void». Вы можете свободно и безопасно конвертировать с void *, но вы не можете без проблем конвертировать до void **.

Таким образом, вы можете просто сделать:

void foo (void* values, int datatype) { 
    if (datatype == 1) { 
     int ** pnvalues = values; 
     *pnvalues = malloc(5 * sizeof int); 

    /* Rest of function */ 
} 

и так далее, а затем вызвать его похожим на:

int * new_int_array; 
foo(&new_int_array, 1); 

&new_int_array имеет тип int **, который получит неявно преобразуется в void * по foo() , и foo() преобразует его обратно в тип int ** и разыгрывает его, чтобы косвенно изменить new_int_array, чтобы указать на новую память, которую он имеет dynamica lly выделено.

Для указатель на динамический массив строк:

void foo (void* values, int datatype) { 

    /* Deal with previous datatypes */ 

    } else if (datatype == 3) { 
     char *** psvalues = values; 
     *psvalues = malloc(5 * sizeof char *); 
     *psvalues[0] = malloc(5); 

    /* Rest of function */ 
} 

и так далее, и называют его:

char ** new_string_array; 
foo(&new_string_array, 3); 

Аналогично, &new_string_array имеет тип char ***, снова получает неявно преобразуется в void *, и foo() преобразует его обратно и косвенно делает new_string_array указывает на вновь выделенные блоки памяти.

+0

OP хочет, чтобы функция 'foo' выделяла память и возвращала выделенную память через параметр out, не требуя, чтобы вызывающая функция выделяла память. Поэтому вам нужно 'void **', чтобы вы могли разыменовать его и вернуть адрес обратно вызывающему. –

+0

@AdamRosenfield: Если первый аргумент, который он передал, является адресом указателя в вызывающем, вам не нужен 'void **', то будет выполняться регулярный 'void *'. Любая функция может динамически распределять память для своего вызывающего абонента, принимая адрес указателя. Если вызывающая функция выполняла 'int * allocate_me; foo (& allocate_me, 1); 'тогда мой первый пример будет работать по мере необходимости. '& allocate_me', конечно, имеет тип' int ** ', который будет неявно преобразовывать в' void * ', а затем' foo() 'преобразует его обратно в' int ** 'и косвенно изменяет' allocate_me'. –

+0

@AdamRosenfield: точка использования 'void *' заключается в том, чтобы избежать неопределенного поведения, которое вы заметили. Если 'foo' принимает' void * ', который действительно может быть' float ** 'или' int ** 'или' char *** 'в зависимости от аргумента' datatype', он может направить его в нужное положение затем сохраните его, сохранив тип указателя, который на самом деле ожидает абонент. –

7

Как следует справляться с такой ситуацией? Могу ли я передать char*** функции, ожидающей void**, но правильно ли ее вставить?

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

Если ваша функция ожидает void**, то вам лучше передать ее void**. Любой тип указателя может быть неявно преобразован в void*, но это работает только на верхнем уровне: char* могут быть преобразованы в void* и char** может быть неявно преобразован в void* (потому что char** является «указатель на char*»), но char**не может преобразуется в void**, а также char*** также не может быть преобразован в void**.

Надлежащий способ вызвать эту функцию, чтобы передать ему надлежащую void**, затем отливали в результате void* указатель назад к своему первоначальному типу:

void foo(void **values, int datatype) 
{ 
    if(datatype == 3) 
    { 
     char ***str_values = ...; 
     *values = str_values; // Implicit cast from char*** to void* 
    } 
    else 
    ... 
} 

... 

void *values; 
foo(&values, 2); 
char ***real_values = (char ***)values; 

Если предположить, что *values фактически указал на char***, то это листинг действителен и не имеет никакого неопределенного поведения в любом из путей кода.

+0

Хм. Это не совпадает с моим пониманием, но я не знаком со стандартом. Можете ли вы предоставить ссылку, указывающую, как значения «char **»; foo (& values, 2) '- это неопределенное поведение, пока ваш пример отсутствует? Я не пытаюсь утверждать, что вы ошибаетесь, просто пытаясь понять, где разрыв в моем понимании. –

+1

@BrianCampbell: В примере Адама '& values' является типом' void ** ', который функция принимает, поэтому нет никакого преобразования. В вашем примере '& values' - это тип' char ***', который явно отличается от типа 'void **'. C11 6.3.2.3.7 говорит: «Указатель на тип объекта может быть преобразован в указатель на другой тип объекта. Если результирующий указатель неправильно выровнен для ссылочного типа, поведение не определено. В противном случае при обратном обратном , результат сравнивается с исходным указателем. ' –

+1

Неопределенное поведение, потому что для реализации разрешено иметь несколько разных представлений для указателей, в зависимости от типа, указанного в типе. Чтобы избежать неопределенного поведения, вам нужно фактически преобразовать любые указатели в или из указателей 'void *', вы не можете просто преобразовать 'void **' и ожидать, что указатели указателей указали, что их типы были преобразованы. Но реализации MOST не имеют множественных представлений указателей, поэтому все в порядке. –

5

A void * - это просто указатель на неуказанный тип; он может быть указателем на int или char или char * или char ** или что угодно, если вы убедитесь, что при разыменовании вы относитесь к нему как к соответствующему типу (или к типу оригинала можно смело интерпретировать как).

Таким образом, a void ** - это просто указатель на void *, который может быть указателем на любой тип, который вы хотите, например char *. Итак, да, если вы выделяете массивы некоторых типов объектов, и в одном случае эти объекты char *, то вы можете использовать void **, чтобы ссылаться на них, предоставляя вам то, что можно назвать char ***.

Это вообще редкость увидеть эту конструкцию непосредственно, потому что обычно вы приложите некоторый тип или информацию о длине в массив, а не имея char *** у вас есть struct typed_object **foo или что-то в этом роде, где struct typed_object имеет тип метки и указатель, и вы отбрасываете указатель, который вы извлекаете из этих элементов, в соответствующие типы, или у вас есть struct typed_array *foo, который является структурой, содержащей тип и массив.

Несколько отмечает о стиле. Во-первых, это может сделать ваш код трудным для чтения. Будьте очень осторожны, чтобы структурировать его и четко документировать, чтобы люди (включая вас самих) могли понять, что происходит. Кроме того, не выдавайте результат malloc; void * автоматически продвигается к типу, которому он назначен, и выдача результата из malloc может привести к тонким ошибкам, если вы забудете включить <stdlib.h> или обновите объявление типа, но забудьте обновить актерский состав. См. this question для получения дополнительной информации.

И обычно это хорошая привычка присоединять * к объявлению к имени переменной, а не к имени типа, так как на самом деле оно анализирует.Следующий объявляет один char и один char *, но если вы пишете это так, как ты их написания, можно было бы ожидать, что объявить два char *:

char *foo, bar; 

Или написано по-другому:

char* foo, bar; 
0

С некоторыми трюками вы можете это сделать. См. Пример:

int sizes[] = { 0, sizeof(int), sizeof(float), sizeof(char *) } 

void *foo(datatype) { 
    void *rc = (void*)malloc(5 * sizes[datatype]); 
    switch(datatype) { 
    case 1: { 
     int *p_int = (int*)rc; 
     for(int i = 0; i < 5; i++) 
     p_int[i] = 1; 
    } break; 
    case 3: { 
     char **p_ch = (char**)rc; 
     for(int i = 0; i < 5; i++) 
     p_ch[i] = strdup("hello"); 
    } break; 
    } // switch 
    return rc; 
} // foo 

В вызывающем абоненте просто передайте возвращаемое значение соответствующему указателю и работайте с ним.

+2

Этот 'void * rc = (void *) malloc (...' принимает 'void *' и переводит его в 'void *', чтобы сохранить его в 'void *'. malloc() 'в лучшем случае невелика, но это доводит до крайности. –

1

Существует встроенный механизм, позволяющий сделать это уже с добавленным бонусом, что он допускает переменное количество аргументов. Это обычно рассматривается в этом формате yourfunc(char * format_string,...)

/*_Just for reference_ the functions required for variable arguments can be defined as: 
#define va_list    char* 
#define va_arg(ap,type)  (*(type *)(((ap)+=(((sizeof(type))+(sizeof(int)-1)) \ 
           & (~(sizeof(int)-1))))-(((sizeof(type))+ \ 
           (sizeof(int)-1)) & (~(sizeof(int)-1))))) 
#define va_end(ap)   (void) 0 
#define va_start(ap,arg) (void)((ap)=(((char *)&(arg))+(((sizeof(arg))+ \ 
           (sizeof(int)-1)) & (~(sizeof(int)-1))))) 
*/ 

Так вот простой пример, который можно использовать со строкой формата и переменным числом аргументов

#define INT '0' 
#define DOUBLE '1' 
#define STRING '2' 

void yourfunc(char *fmt_string, ...){ 
    va_list args; 
    va_start (args, fmt_string); 
    while(*fmt_string){ 
    switch(*fmt_string++){ 
    case INT: some_intfxn(va_arg(ap, int)); 
    case DOUBLE: some_doublefxn(va_arg(ap, double)); 
    case STRING: some_stringfxn(va_arg(ap, char *)); 
    /* extend this as you like using pointers and casting to your type */ 
    default: handlfailfunc(); 
    } 
    } 
    va_end (args); 
} 

Таким образом, вы можете запустить его: yourfunc("0122",42,3.14159,"hello","world"); или так как вам нужно только 1, чтобы начать с yourfunc("1",2.17); Это не становится намного более общим, чем это. Вы могли бы даже настроить несколько целых типов, чтобы сказать, чтобы он запускал другой набор функций для этого конкретного целого. Если format_string слишком утомительно, тогда вы можете так же легко использовать int datatype, но у вас будет ограничение до 1 arg (технически вы можете использовать бит ops для типа OR num_args, но я отвлекся)

один тип один значение форма:

#define INT '0' 
#define DOUBLE '1' 
#define STRING '2' 

void yourfunc(datatype, ...){ /*leaving "..." for future while on datatype(s)*/ 
    va_list args; 
    va_start (args, datatype); 
    switch(datatype){ 
    case INT: some_intfxn(va_arg(ap, int)); 
    case DOUBLE: some_doublefxn(va_arg(ap, double)); 
    case STRING: some_stringfxn(va_arg(ap, char *)); 
    /* extend this as you like using pointers and casting to your type */ 
    default: handlfailfunc(); 
    } 
    va_end (args); 
} 
Смежные вопросы