универсальное программирование в C с пустым указателем

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

10
задан Benjamin 13 December 2013 в 22:44
поделиться

4 ответа

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

9
ответ дан 3 December 2019 в 23:12
поделиться

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

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

-2
ответ дан 3 December 2019 в 23:12
поделиться

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

comp.lang.c FAQ list - Question 4.9

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

void f(void **);
double *dp;
f((void **)&dp);

A: Не переносимо. Подобный код может работать и иногда рекомендуется, но он полагается на то, что все типы указателей имеют одинаковое внутреннее представление (что является общим, но не универсальным; см. вопрос 5.17).

В Си не существует общего типа указателя на указатель. void * действует как общий указатель только потому, что преобразования (если необходимо) применяются автоматически, когда другие типы указателей присваиваются к и от void * 's; эти преобразования не могут быть выполнены, если сделана попытка коснуться значения 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 */

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

4
ответ дан 3 December 2019 в 23:12
поделиться

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

1
ответ дан 3 December 2019 в 23:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: