общее программирование на C с указателем void

Несмотря на то, что можно написать общий код на C, используя указатель void (универсальный указатель), я считаю, что отладить код довольно сложно, поскольку указатель void может принимать любой тип указателя без предупреждения компилятора. (например, функция foo () принимает указатель void, который должен быть указателем на структуру, но компилятор не будет жаловаться, если передан массив char.) Какой подход / стратегию вы все используете при использовании указателя void в C?


person Nyan    schedule 16.03.2010    source источник
comment
Если это вопрос только для C, удалите тег C ++   -  person Martin York    schedule 16.03.2010
comment
Хммм ... Когда я бью по пальцу молотком, очень больно. Какой прием вы используете, когда ударяете молотком по частям тела?   -  person sharptooth    schedule 16.03.2010
comment
Если вы думаете, что универсальное программирование на C с void * может быть элегантным / приятным в использовании, посмотрите, что вам нужно сделать, чтобы использовать функцию qsort ...   -  person Matteo Italia    schedule 16.03.2010
comment
почему вас так беспокоит указатель void, когда у вас много конкретных типов данных ... :)   -  person wrapperm    schedule 16.03.2010
comment
Разве использование шаблонов C ++ не является приемлемым вариантом? Если вас беспокоит универсальное программирование, вам обязательно стоит подумать об этом.   -  person Konrad Rudolph    schedule 16.03.2010
comment
@wrapperm с использованием указателей void может быть действительно экономичным, особенно если вам нужна общая структура данных.   -  person systemsfault    schedule 12.08.2010
comment
Я провел некоторое исследование в области общего программирования с использованием C. Все свои выводы я обобщил в этой статье: andreinc.net/2010/09/30/generic-data-structures-in-c Может быть, это поможет.   -  person Andrei Ciobanu    schedule 03.10.2010
comment
@ Андрей: Это могло быть ответом   -  person u0b34a0f6ae    schedule 30.10.2011


Ответы (6)


Решение состоит в том, чтобы не использовать void*, если в этом нет необходимости. Места, где на самом деле требуется указатель void, очень малы: параметры для функций потоков и несколько других мест, где вам нужно передать данные, зависящие от реализации, через универсальную функцию. В любом случае код, который принимает параметр void*, должен принимать только один тип данных, переданный через указатель void, и этот тип должен быть задокументирован в комментариях и подчиняться всем вызывающим.

person JSBձոգչ    schedule 16.03.2010
comment
OP специально упомянул универсальное программирование, что является довольно хорошим вариантом использования void * (в отсутствие языковых средств более высокого уровня, таких как шаблоны или хорошие макросы). - person Matt Curtis; 11.05.2010
comment
Я имею в виду, например, qsort () и bsearch () - person Matt Curtis; 11.05.2010
comment
@Matt, qsort и bsearch являются примерами данных, зависящих от реализации, через общую функцию. - person JSBձոգչ; 11.05.2010
comment
@JS: Верно, и когда вы занимаетесь общим программированием, количество мест, которые вам нужно сделать, ближе к полному грузовику, чем к горстке ;-) - person Matt Curtis; 12.05.2010

Это может помочь:

Список часто задаваемых вопросов 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 */

(этот код аналогичен фрагменту в вопросе), маловероятно, что он сработает.

person Robert S. Barnes    schedule 16.03.2010

Решение Арьи можно немного изменить, чтобы поддерживать переменный размер:

#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;
}
person Jingguo Yao    schedule 31.07.2012

Подход / стратегия состоит в том, чтобы минимизировать использование указателей void *. Они нужны в конкретных случаях. Если вам действительно нужно передать void *, вы также должны передать размер цели указателя.

person Andrey    schedule 16.03.2010

Эта универсальная функция подкачки очень поможет вам в понимании универсального 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;
}
person Anil Kumar Arya    schedule 01.06.2012

Все мы знаем, что система типов C - это в основном чушь, но старайтесь этого не делать ... У вас все еще есть несколько вариантов работы с универсальными типами: объединения и непрозрачные указатели.

В любом случае, если универсальная функция принимает в качестве параметра указатель void, она не должна пытаться разыменовать его!

person fortran    schedule 16.03.2010