Несмотря на то, что можно писать общий код в C с помощью указателя void (общий указатель), я считаю, что отлаживать код довольно сложно, поскольку указатель void может принимать любой тип указателя без предупреждения из компилятора. (например, функция foo() принимает указатель void, который должен быть указателем на struct, но компилятор не будет жаловаться, если передан массив char.) Какой подход/стратегия вы используете при использовании указателя void в C?
Общее программирование на C с указателем void
Ответ 1
Решение не должно использовать void*
, если вам действительно не нужно. Места, где действительно требуется указатель void, очень малы: параметры для функций потоков и несколько других мест, где вам нужно передать данные, специфичные для реализации, через общую функцию. В каждом случае код, принимающий параметр void*
, должен принимать только один тип данных, передаваемый через указатель void, и тип должен быть документирован в комментариях и рабски подчиняться всем вызывающим абонентам.
Ответ 2
Это может помочь:
comp.lang.c Список вопросов · Вопрос 4.9
Q: Предположим, что я хочу написать функцию, которая принимает общий указатель как аргумент, и я хочу, чтобы имитировать передачу его по ссылке. Могу ли я указать формальный тип параметра void ** и сделать что-то вроде этого?
void f(void **);
double *dp;
f((void **)&dp);
A: Не переносимо. Подобный код может работать и иногда рекомендуется, но он опирается на все типы указателей, имеющие такое же внутреннее представление (которое является общим, но не универсальным, см. Вопрос 5.17).
Нет никакого общего типа указателя на указатель в C. void * действует как общий указатель только потому, что преобразования (если необходимо) применяются автоматически, когда другим типам указателей присваиваются и из void *; эти преобразования не могут быть выполнены, если попытка сделана косвенной по значению void **, которое указывает на тип указателя, отличный от void *. Когда вы используете значение указателя void ** (например, когда вы используете оператор * для доступа к значению void *, на которое указывает void **), компилятор не знает, было ли это значение void * преобразованный из некоторого другого типа указателя. Он должен предположить, что это не что иное, как пустота *; он не может выполнять никаких неявных преобразований.
Другими словами, любое значение void **, с которым вы играете, должно быть где-то адресом фактического значения void *; такие как (void **) и dp, хотя они могут закрыть компилятор, являются непереносимыми (и даже не могут делать то, что вам нужно, см. также вопрос 13.9). Если указатель, который указывает void **, не является void *, и если он имеет другой размер или представление, чем void *, то компилятор не сможет получить к нему доступ правильно.
Чтобы сделать фрагмент кода выше, вам нужно будет использовать промежуточную переменную void *:
double *dp;
void *vp = dp;
f(&vp);
dp = vp;
Назначения в и из vp дают компилятору возможность выполнять любые преобразования, если это необходимо.
Опять же, обсуждение до сих пор предполагает, что разные типы указателей могут иметь разные размеры или представления, что редко встречается сегодня, но не неслыханно. Чтобы лучше понять проблему с void **, сравните ситуацию с аналогичной, включая, например, типы int и double, которые, вероятно, имеют разные размеры и, разумеется, имеют разные представления. Если у нас есть функция
void incme(double *p)
{
*p += 1;
}
тогда мы можем сделать что-то вроде
int i = 1;
double d = i;
incme(&d);
i = d;
и я будет увеличиваться на 1. (Это аналогично правильному коду void **, содержащему вспомогательный vp.) Если, с другой стороны, мы пытались что-то вроде
int i = 1;
incme((double *)&i); /* WRONG */
(этот код аналогичен фрагменту в вопросе), было бы маловероятно работать.
Ответ 3
Решение Arya можно немного изменить, чтобы поддерживать переменный размер:
#include <stdio.h>
#include <string.h>
void swap(void *vp1,void *vp2,int size)
{
char buf[size];
memcpy(buf,vp1,size);
memcpy(vp1,vp2,size);
memcpy(vp2,buf,size); //memcpy ->inbuilt function in std-c
}
int main()
{
int array1[] = {1, 2, 3};
int array2[] = {10, 20, 30};
swap(array1, array2, 3 * sizeof(int));
int i;
printf("array1: ");
for (i = 0; i < 3; i++)
printf(" %d", array1[i]);
printf("\n");
printf("array2: ");
for (i = 0; i < 3; i++)
printf(" %d", array2[i]);
printf("\n");
return 0;
}
Ответ 4
Подход/стратегия заключается в том, чтобы минимизировать использование указателей void *. Они необходимы в конкретных случаях. Если вам действительно нужно передать void *, вы также должны передать размер указателя.
Ответ 5
Эта общая функция подкачки поможет вам в понимании общей недействительности *
#include<stdio.h>
void swap(void *vp1,void *vp2,int size)
{
char buf[100];
memcpy(buf,vp1,size);
memcpy(vp1,vp2,size);
memcpy(vp2,buf,size); //memcpy ->inbuilt function in std-c
}
int main()
{
int a=2,b=3;
float d=5,e=7;
swap(&a,&b,sizeof(int));
swap(&d,&e,sizeof(float));
printf("%d %d %.0f %.0f\n",a,b,d,e);
return 0;
}
Ответ 6
Мы все знаем, что система типов C в основном дерьмовая, но старайтесь этого не делать... У вас все еще есть некоторые варианты решения общих типов: объединения и непрозрачные указатели.
В любом случае, если универсальная функция принимает указатель на void как параметр, не следует пытаться разыменовать ее!