Несмотря на то, что можно написать общий код на C, используя указатель void (универсальный указатель), я считаю, что отладить код довольно сложно, поскольку указатель void может принимать любой тип указателя без предупреждения компилятора. (например, функция foo () принимает указатель void, который должен быть указателем на структуру, но компилятор не будет жаловаться, если передан массив char.) Какой подход / стратегию вы все используете при использовании указателя void в C?
общее программирование на C с указателем void
Ответы (6)
Решение состоит в том, чтобы не использовать void*
, если в этом нет необходимости. Места, где на самом деле требуется указатель void, очень малы: параметры для функций потоков и несколько других мест, где вам нужно передать данные, зависящие от реализации, через универсальную функцию. В любом случае код, который принимает параметр void*
, должен принимать только один тип данных, переданный через указатель void, и этот тип должен быть задокументирован в комментариях и подчиняться всем вызывающим.
Это может помочь:
Список часто задаваемых вопросов comp.lang.c · Вопрос 4.9
Вопрос: Предположим, я хочу написать функцию, которая принимает в качестве аргумента универсальный указатель, и я хочу имитировать передачу его по ссылке. Могу ли я указать тип формального параметра 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 и от vp дают компилятору возможность при необходимости выполнять любые преобразования.
Опять же, обсуждение до сих пор предполагает, что разные типы указателей могут иметь разные размеры или представления, что сегодня редко, но не неслыханно. Чтобы более четко оценить проблему с void **, сравните ситуацию с аналогичной ситуацией, в которой используются, скажем, типы int и double, которые, вероятно, имеют разные размеры и, безусловно, имеют разные представления. Если у нас есть функция
void incme(double *p)
{
*p += 1;
}
тогда мы можем сделать что-то вроде
int i = 1;
double d = i;
incme(&d);
i = d;
и i будет увеличен на 1. (Это аналог правильного кода void **, включающего вспомогательный vp.) Если, с другой стороны, мы должны были попытаться что-то вроде
int i = 1;
incme((double *)&i); /* WRONG */
(этот код аналогичен фрагменту в вопросе), маловероятно, что он сработает.
Решение Арьи можно немного изменить, чтобы поддерживать переменный размер:
#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;
}
Подход / стратегия состоит в том, чтобы минимизировать использование указателей void *. Они нужны в конкретных случаях. Если вам действительно нужно передать void *, вы также должны передать размер цели указателя.
Эта универсальная функция подкачки очень поможет вам в понимании универсального void *
#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;
}
Все мы знаем, что система типов C - это в основном чушь, но старайтесь этого не делать ... У вас все еще есть несколько вариантов работы с универсальными типами: объединения и непрозрачные указатели.
В любом случае, если универсальная функция принимает в качестве параметра указатель void, она не должна пытаться разыменовать его!