Подтвердить что ты не робот

Как создать общую функцию, используя void * в c?

У меня есть функция incr, чтобы увеличить значение на 1 Я хочу сделать его общим, потому что я не хочу создавать разные функции для одной и той же функциональности.

Предположим, что я хочу увеличивать int, float, char на 1

void incr(void *vp)
{
        (*vp)++;
}

Но проблема, которую я знаю, Dereferencing a void pointer is undefined behaviour. Иногда это может привести к ошибке: Invalid use of void expression.

My main funciton:

int main()
{

int i=5;
float f=5.6f;
char c='a';

incr(&i);
incr(&f);
incr(&c);

return 0;
}

Проблема в том, как это решить? Есть ли способ решить его только в C

или

мне нужно определить incr() для каждого типа данных? если да, то какое использование void *

Такая же проблема с swap() и sort(). Я хочу поменять местами и отсортировать все типы данных с одинаковой функцией.

4b9b3361

Ответ 1

Вы можете реализовать первый как макрос:

#define incr(x) (++(x))

Конечно, это может иметь неприятные побочные эффекты, если вы не будете осторожны. Это касается единственного метода C, который позволяет применять одну и ту же операцию к любому из множества типов. В частности, поскольку макрос реализован с использованием подстановки текста, к тому моменту, когда компилятор видит его, у вас есть буквальный код ++whatever;, и он может применить ++ правильно для типа элемента, который вы предоставили. С указателем на void вы не знаете много (если вообще что-либо) о фактическом типе, поэтому вы не можете много манипулировать этими данными).

void * обычно используется, когда соответствующая функция действительно не нуждается в точном типе данных. В некоторых случаях (например, qsort) он использует функцию обратного вызова, чтобы избежать необходимости знать какие-либо детали данных.

Поскольку он выполняет сортировку и свопинг, давайте посмотрим на qsort немного подробнее. Его подпись:

void qsort(void *base, size_t nmemb, size_t size,
           int(*cmp)(void const *, void const *));

Итак, первый - это void *, о котором вы просили - указатель на данные, которые нужно отсортировать. Второй указывает qsort количество элементов в массиве. Третий - размер каждого элемента массива. Последнее является указателем на функцию, которая может сравнивать отдельные элементы, поэтому qsort не нужно знать, как это сделать. Например, где-то внутри qsort будет некоторый код, например:

// if (base[j] < base[i]) ...
if (cmp((char *)base+i, (char *)base+j) == -1)

Аналогичным образом, чтобы поменять два элемента, он обычно имеет локальный массив для временного хранения. Затем он будет копировать байты от array[i] до его темпа, затем от array[j] до array[i] и, наконец, от temp до array[j]:

char temp[size];

memcpy(temp, (char *)base+i, size);              // temp = base[i]
memcpy((char *)base+i, (char *)base+j, size);    // base[i] = base[j]
memcpy((char *)base+j, temp, size);              // base[j] = temp

Ответ 2

Использование void * не даст вам полиморфного поведения, которое, как я думаю, вы ищете. void * просто позволяет вам обходить проверку типов переменных кучи. Чтобы достичь фактического полиморфного поведения, вам нужно будет передать информацию о типе в качестве другой переменной и проверить ее в своей функции incr, затем нарисуйте указатель на нужный тип ИЛИ, передав любые операции с вашими данными в качестве указателей функций ( другие упоминали qsort в качестве примера). C не имеет автоматического полиморфизма, встроенного в язык, поэтому вам было бы симулировать его. За кулисами языки, которые строят в полиморфизме, делают что-то вроде этого за кулисами.

Чтобы разработать, void * - это указатель на общий блок памяти, который может быть любым: int, float, string и т.д. Длина блока памяти даже не сохраняется в указателе, пусть один тип данных. Помните, что внутренне все данные являются битами и байтами, а типы действительно являются просто маркерами для физического кодирования логических данных, поскольку по сути, биты и байты являются беспричинными. В C эта информация не хранится с переменными, поэтому вы должны предоставить ее самому компилятору, чтобы он знал, следует ли применять операции для обработки битовых последовательностей как 2 целых числа дополнений, с плавающей запятой с двойной точностью IEEE 754, символ ASCII данные, функции и т.д.; это все конкретные стандарты форматов и операций для разных типов данных. Когда вы прикладываете void * к указателю на определенный тип, вы, как программист, утверждаете, что данные, на которые указывает, действительно, относятся к типу, на который вы его производите. В противном случае вы, вероятно, находитесь в странном поведении.

Так что же хорошего для void *? Это полезно для обработки блоков данных без учета типа. Это необходимо для таких вещей, как выделение памяти, копирование, операции с файлами и передача указателей на функции. Однако почти во всех случаях программист С ретранслирует из этого низкоуровневого представления как можно больше, структурируя свои данные с типами, которые имеют встроенные операции; или используя структуры, причем операции над этими структурами, определенными программистом как функции.

Вы можете захотеть ознакомиться с объяснением Wikipedia для получения дополнительной информации.

Ответ 3

Вы не можете делать именно то, что вы просите - операторы, такие как приращение, должны работать с определенным типом. Итак, вы можете сделать что-то вроде этого:

enum type { 
    TYPE_CHAR,
    TYPE_INT,
    TYPE_FLOAT
};

void incr(enum type t, void *vp)
{
    switch (t) {
        case TYPE_CHAR:
        (*(char *)vp)++;
        break;

        case TYPE_INT:
        (*(int *)vp)++;
        break;

        case TYPE_FLOAT:
        (*(float *)vp)++;
        break;
    }
}

Тогда вы назовете это так:

int i=5;
float f=5.6f;
char c='a';

incr(TYPE_INT, &i);
incr(TYPE_FLOAT, &f);
incr(TYPE_CHAR, &c);

Конечно, это не дает вам ничего, кроме определения отдельных функций incr_int(), incr_float() и incr_char() - это не цель void *.

Цель void * реализуется, когда алгоритм, который вы пишете, не заботится о реальном типе объектов. Хорошим примером является стандартная функция сортировки qsort(), которая объявляется как:

void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *));

Это можно использовать для сортировки массивов любого типа объекта - вызывающему абоненту просто нужно предоставить функцию сравнения, которая может сравнивать два объекта.

Обе функции swap() и sort() попадают в эту категорию. swap() еще проще - алгоритм не должен знать ничего, кроме размера объектов для их замены:

void swap(void *a, void *b, size_t size)
{
    unsigned char *ap = a;
    unsigned char *bp = b;
    size_t i;

    for (i = 0; i < size; i++) {
        unsigned char tmp = ap[i];

        ap[i] = bp[i];
        bp[i] = tmp;
    }
}

Теперь, учитывая любой массив, вы можете поменять местами два элемента в этом массиве:

int ai[];
double ad[];

swap(&ai[x], &ai[y], sizeof(int));
swap(&di[x], &di[y], sizeof(double));

Ответ 4

Пример использования "общего" свопа.

Этот код заменяет два блока памяти.

void memswap_arr(void* p1, void* p2, size_t size)
{      
      size_t         i;
      char* pc1= (char*)p1;
      char* pc2= (char*)p2;
      char  ch;

      for (i= 0; i<size; ++i) {
        ch=     pc1[i];
        pc1[i]= pc2[i];
        pc2[i]= ch;
      }
}

И вы называете это следующим образом:

int main() {
     int i1,i2;
     double d1,d2;
     i1= 10; i2= 20;
     d1= 1.12; d2= 2.23;
     memswap_arr(&i1,&i2,sizeof(int));     //I use memswap_arr to swap two integers
     printf("i1==%d i2==%d \n",i1,i2);     //I use the SAME function to swap two doubles
     memswap_arr(&d1,&d2,sizeof(double));      
     printf("d1==%f d2==%f \n",d1,d2);
     return 0;
}

Я думаю, что это должно дать вам представление о том, как использовать одну функцию для разных типов данных.

Ответ 5

Прежде чем разыгрывать его, вы должны направить указатель на конкретный тип. Поэтому вы также должны добавить код для передачи того, что является типом переменной указателя.

Ответ 6

Извините, если это может произойти как не ответе на широкий вопрос " Как сделать общую функцию с помощью void * в c?".. но проблемы, которые у вас есть (увеличение переменная произвольного типа и замена 2 переменных неизвестных типов) может быть намного проще с макросами, чем с функциями и указателями на void.

Приращение достаточно просто:

#define increment(x) ((x)++)

Для замены я сделал бы что-то вроде этого:

#define swap(x, y)                  \
({                                  \
        typeof(x) tmp = (x);        \
        (x) = (y);                  \
        (y) = tmp;                  \
})

..., который работает для ints, double и char указателей (строк), основанных на моем тестировании.

В то время как инкрементный макрос должен быть довольно безопасным, макрос подкачки полагается на оператор typeof(), который является расширением GCC/clang, НЕ является частью стандартного C (если вы только когда-либо компилируете gcc или clang, это не должно быть слишком большой проблемой).

Я знаю, что уклонился от первоначального вопроса; но, надеюсь, он по-прежнему решает ваши первоначальные проблемы.

Ответ 7

Вы можете использовать типовые средства (стандарт C11). Если вы намереваетесь использовать более продвинутые математические функции (более продвинутые, чем оператор ++), вы можете перейти к <tgmath.h>, который является типичным определением функций в <math.h> и <complex.h>.

Вы также можете использовать ключевое слово _Generic для определения универсальной функции типа как макроса. Ниже пример:

#include <stdio.h>

#define add1(x) _Generic((x), int: ++(x), float: ++(x), char: ++(x), default: ++(x))

int main(){
  int i = 0;
  float f = 0;
  char c = 0;

  add1(i);
  add1(f);
  add1(c);

  printf("i = %d\tf = %g\tc = %d", i, f, c);
}

Вы можете найти дополнительную информацию о языке стандарта и более сложных примерах в этом сообщении из Rob blog программирования.

Что касается вопросов * void, вопросов подкачки и сортировки, лучше обратитесь к Jerry Coffin.