LINUX.ORG.RU

А как на C решаются проблемы одной переменной на несколько типов?

 


0

2

Возник вопрос. Можно ли в C использовать переменную произвольного типа в зависимости от контекста? Пример кода.

void test(int type) {

 if (type==1) {
  char *data = "Hello world";
 }
 if (type==2) {
  int data = 123;
 }
// ...
}

Экспериментировал с void. Работает лишь наполовину

void test(int type) {

 void *data;
 if (type==1) {
  char *data = "Hello world";
  printf("Data: %s\n",data); // Тут data - правильные
 }
 if (type==2) {
  int data = 123;
 }
// ...
 printf("Data: %s\n",data); // А вот тут data - поломанные
}

Первый printf выводит как положено, Hello world.

А вот второй printf вне условия, выводит �ÐUH��H�� H�}�H�

Благодарю.

★★★★★

ты с какого языка в си приехал? у тебя три переменных data в разных областях видимости.

int data или void* data… это декларация НОВОЙ переменной c именем data и указанным типом в данном блоке, то есть резервирование под нее места, в данном случае на стеке.

ты что хочешь сказать своим кодом?

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)

Нет, так это не работает, хуже того, это undefined behaviour.

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

Если так уж сильно надо, чтобы в одной области памяти можно было хранить несколько разных типов значений, то нужен union и аккуратная работа с этой переменной этого union’а, чтобы случайно не воспользоваться типом A, когда в переменной записаны данные типа B. Иначе опять undefined behaviour.

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

Ну и наконец, если нужно что-то типа шаблонов из C++, то, наверное, можно попробовать макросы, но выстрелить себе в ногу становится еще легче, чем обычно.

squareroot ★★★★
()

man union. Но тебе надо знать, что внутри переменной (например, в другой переменной хранить какой-то флаг, который говорит о том число ли там или строка). А в твоём примере ты объявил 3 разных переменных. Первая data не инициализированна, соответственно, выводится бред.

KivApple ★★★★★
()

Окей, отвечаю всем и сразу:

Есть такая говняшка, называется D-BUS.

В их С-реализации есть функция, dbus_message_append_args.

Эта функция принимает в себя условно говоря четыре параметра: соединение, тип переменной, сама переменная, ну и завершающий тип необходимый для работы функции. Неважное вычеркнул.

Например:

int data = 12345; dbus_message_append_args(connection, DBUS_TYPE_INT32, &data, DBUS_TYPE_INVALID);

Или:

char* data = "12345"; dbus_message_append_args(connection, DBUS_TYPE_STRING, &data, DBUS_TYPE_INVALID);

Или:

double data = 3.14; dbus_message_append_args(connection, DBUS_TYPE_DOUBLE, &data, DBUS_TYPE_INVALID);

Задача: обернуть эту функцию в другую функцию, принимающую в себя тип переменной и саму переменную.

void function my_function (int type, хз_какой_тип_data) {
 dbus_message_append_args(connection, type, &хз_какой_тип_data, DBUS_TYPE_INVALID);
}

В моем случае задача будет усложнена, но хотя бы тут найти решение.

windows10 ★★★★★
() автор топика
Ответ на: комментарий от sergej

хз_какой_тип_data как раз и указан в описании функции dbus_message_append_args, но тут я думаю можно использовать void*, который по ситуации будет приводиться к int*, char*, double*

Не приводится, увы.

windows10 ★★★★★
() автор топика
Ответ на: комментарий от sergej

Мне нужно немножко не это.

Мне нужно, чтобы data передавался не в параметрах функции, а объявлялся в самой этой функции.

Пример я привел в топике, но с char* такое не прокатывает.

Если это невозможно - то ничего страшного.

windows10 ★★★★★
() автор топика
Ответ на: комментарий от windows10

Не приводится, увы.

void* для того и создан, чтобы указывать на ЛЮБОЙ тип, и потом кастовать к нужному. иначе void* не нужен вообще.

но void* это адрес куска памяти, где лежит обьект неясного типа.

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

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от sergej

printf печатает херню не потому что double плохо передалась, а потому что в формате %s указан (это если предположить, что 2й принтф внутри второго ифа, а не так как по написано в примере)

sergej ★★★★★
()
Ответ на: комментарий от sergej

ну или если очень нужна одна переменная

char *c = "qweqwe";
double d = 123;

void *p;

p = c;
dbus_message_append_args(connection, DBUS_TYPE_STRING, p, DBUS_TYPE_INVALID);

p = &d;
dbus_message_append_args(connection, DBUS_TYPE_DOUBLE, p, DBUS_TYPE_INVALID);
sergej ★★★★★
()
Ответ на: комментарий от sergej

Вообще не проблема сделать с десяток dbus_message_append_args через if или switch\case для каждого типа. Просто хотелось изящного решения чтобы не плодить простыню.

windows10 ★★★★★
() автор топика
Ответ на: комментарий от windows10

Вообще не проблема сделать с десяток dbus_message_append_args через if или switch\case для каждого типа.

так тебе надо?

void append_int(ConnType* conn, int value) {
  dbus_message_append_args(conn, DBUS_TYPE_INT, &value, DBUS_TYPE_INVALID);
}

void append_double(ConnType* conn, double value) {
  dbus_message_append_args(conn, DBUS_TYPE_DOUBLE, &value, DBUS_TYPE_INVALID);
}
alysnix ★★★
()
Ответ на: комментарий от sergej

Если авторы библиотеки не сделали вариантные типы и не написали эту простыню, то придётся писать самому, но тут непонятна изначальная проблема.

Изначальная проблема - мерзкий d-bus, в который разработчики зачем-то встроили разные типы переменных, хотя все они в конечном итоге хранятся в строках, а специфичных вычислений основанных на типах переменных там нет.

К примеру в мускуле тоже есть разные типы переменных, но там это оправдано наличием операторов. Ну там если это int, то можно искать в диапазоне, можно складывать и вычитать. Если это datetime, то можно искать раньше\позже и тд итп.

Хотели наверное упростить жизнь программистам, но теперь в программах вместо блока, переводящего char* в нужный формат, появился блок переводящий их особую, уличную магию в нужный формат.

Чтобы не таскать за собой лишнее говно, решил чутка оптимизировать. Не судьба.

windows10 ★★★★★
() автор топика
Ответ на: комментарий от MOPKOBKA

https://docs.gtk.org/gobject/gvalue.html - тут даже пример на твой похож.
https://docs.gtk.org/gobject/struct.Value.html

Не, я решил сьехать с gobject в сторону нативного libdbus. Уж очень много лишнего там.

Изучаю все это, и шокирован. Такой простой механизм как IPC, и такие велосипеды, ужс

windows10 ★★★★★
() автор топика
Ответ на: комментарий от windows10

Ну тут главное сам принцип а не библиотека. GValue может быть и строкой, и числом, и еще всяким разным.

Ну и вот тебе самый простой вариант для понимания:

enum variant_type {
  VARIANT_INTEGER,
  VARIANT_REAL,
  VARIANT_STRING
};

struct variant {
  enum variant_type type;
  union {
    int integer;
    float real;
    char *string;
  };
};

void print_variant(struct variant *self)
{
  switch (self) {
  case VARIANT_INTEGER:
    printf("%d\n", self->integer);
    break;
  case VARIANT_REAL:
    printf("%f\n", self->real);
    break;
  case VARIANT_STRING:
    printf("%s\n", self->string);
    break;
  }
}

struct variant *variant_new_integer(int value);

struct variant *variant_new_real(float value);

struct variant *variant_new_string(const char *value);

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 3)
Ответ на: комментарий от squareroot

случайно не воспользоваться типом A, когда в переменной записаны данные типа B. Иначе опять undefined behaviour.

Вроде, начиная с C99 это разрешено. Источник, первая секция “since C99.”

kaldeon
()
Ответ на: комментарий от alysnix

так тебе надо?

void append_int

void append_double

Да, сделать 2-3 отдельные функции для передачи строготипизированных параметров разных типов — единственно верный вариант. Все остальное говнокод.

У языка есть строгая типизиация, нафига ее ломать и превращать C в JS с простынями кода для детекта в рантайме типа, который прилетел в функцию.

DBus, скорее всего, плевать на содержимое передаваемых данных, это просто механизм межпроцессного взаимодействия, он должен иметь биндинги на любом языке, хоть на C, хоть на Java, хоть на Паскале, поэтому у него кривая примитивная апишка, манипулирующая буферами байт. Но ты то находишься внутри одного своего языка — сделай удобные обертки идиоматические для твоего языка. То что будет пару строчек дублирования кода — ничего страшного, зато надежно, быстро и понятно.

anonymous
()
Ответ на: комментарий от windows10

Задача: обернуть эту функцию в другую функцию, принимающую в себя тип переменной и саму переменную.

В Си это делается с помощью tagged union, примерно так:

struct v {
  union {
    int i;
    long j;
  };
  int type;
} v1;

void my_function (v1 *data) {
  if (data->type == TYPE_INT) {
    dbus_message_append_args(connection, type, data->i, DBUS_TYPE_INT);         
  } else if (data->type == TYPE_LONG) {
    dbus_message_append_args(connection, type, data->j, DBUS_TYPE_LONG);
  }
}

либо, если тег у тебя уже есть, то простой union.

Lrrr ★★★★★
()
Последнее исправление: Lrrr (всего исправлений: 3)
void *arg_type(int type) {
    if (type == 1)
        return char *msg = ”Hello, world!\n";
    if (type == 2)
        return int *data = 123;
}

int main(void) {
    printf("Data is: %s\n", arg_type(1));
    printf("Data is: %d\n", arg_type(2));

    return 0;
}

Ну, или явно использовать приведение типов… А еще, typeof же, нет?

yars068 ★★★★★
()
Последнее исправление: yars068 (всего исправлений: 2)
Ответ на: комментарий от anonymous

Да, сделать 2-3 отдельные функции для передачи строготипизированных параметров разных типов — единственно верный вариант.

Что бы раздувать размер кода машинного и С? Лучше тогда сгенерировать код из интерфейса dbus. В glib скорее всего уже есть хорошая интеграция с dbus, что бы экспортировать в него свои объекты, или работать с ним как с объектами gobject.

У языка есть строгая типизиация, нафига ее ломать и превращать C в JS

Классический пример языка со слабой типизацией, это как раз С. Ты наверное хотел сказать про статическую типизацию.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)

ещё у тебя %s везде, для других типов другие спецификаторы

на практике принято не использовать union/каст а тратить рантайм ресурсы на отдельные переменные и на malloc() + free() и надеяться что они закешированы

anonymous
()

появилась такая приятная вещь как _Generic и теги DBUS можно легко выводить из типов переменных

иллюстативно:

#include <stdio.h>
#include <stddef.h>
enum MY_TYPES {
	TYPE_INT32,
	TYPE_INT64,
	TYPE_DOUBLE,
	TYPE_UNKNOWN
};
#define GET_MY_TYPE(x) _Generic((x),short:TYPE_INT32,int:TYPE_INT64,double:TYPE_DOUBLE,default:TYPE_UNKNOWN)

void fake_dbus2(int type,void *data)
{
	printf("tag %d dataptr=%p",type,data);
}

#define fake_dbus(x) fake_dbus2(GET_MY_TYPE(x),(void *)&(x))

int main() 
{
	int intval=11234;
	short shortval=8955;
	double doubleval=-1.234546;
	fake_dbus(intval);
	fake_dbus(shortval);
	fake_dbus(doubleval);
}

MKuznetsov ★★★★★
()
Ответ на: комментарий от kaldeon

Вроде, начиная с C99 это разрешено. Источник, первая секция “since C99.”

Согласен, но в общем случае, можно получить UB, если например, union состоит из int’а и какого-нибудь указателя, и, сохранив туда int, попытаться разыменовать указатель.

Я хотел подчеркнуть, что нельзя надеяться на то, что Си за тебя будет приводить типы в union’е.

squareroot ★★★★
()
Ответ на: комментарий от windows10
double data = 3.14; dbus_message_append_args(connection, DBUS_TYPE_DOUBLE, &data, DBUS_TYPE_INVALID);

Задача: обернуть эту функцию в другую функцию, принимающую в себя тип переменной и саму переменную.

#include <stdarg.h>

void my_function(int type, ...) {
 va_list args;
 va_start(args, type);
 switch (type) {
 case type_int: { // подразумеваем, что где-то есть enum type_int или #define type_int
  int arg = va_arg(args, int);
  // ...
  break;
 }
 case type_double: {
  double arg = va_arg(args, double);
  // ...
  break;
 }
 // ...
 }
 va_end(args); // стандарт требует, чтобы это было выполнено
}

https://cppreference.com/w/c/variadic.html

anonymous
()
Ответ на: комментарий от windows10

Мне кажется, тут не по Сеньке шапка. Не стоило браться за эту работу с такими вводными, с текущими знаниями Си. Сдавайте мандат и идите в центр занятости.

Сделать это легко. Указатель на любые данные – всегда указатель. Он всегда будет одного размера. А вот чтобы правильно итерироваться по типу, на что ссылается указатель, для этого и нужно знать тип данных. В функцию надо передать указатель на данные void* и в каком-то виде тип данных. На основании переданного типа данных уже и работать с указателем.

hibou ★★★★★
()
Ответ на: комментарий от MOPKOBKA

Что бы раздувать размер кода машинного и С?

Ничего ты там не раздуваешь. Если писать враперы для каждого типа (если их не сто с лишком конечно)и вызывать их, то тебе не надо передавать тип и всякую ненужную требуху в каждом вызове. Это и сократит общий код, если таких вызовов много и даст проверку типов.

alysnix ★★★
()
Ответ на: комментарий от alysnix

Если писать враперы для каждого типа (если их не сто с лишком конечно)и вызывать их, то тебе не надо передавать тип и всякую ненужную требуху в каждом вызове.

А ему точно известно какой тип имеют данные в момент вызова предполагаемого враппера? Если нет, придется заражать статичной болезнью все функции которые работают с динамическими данными.

MOPKOBKA ★★★★★
()
Ответ на: комментарий от hibou

Мне кажется, тут не по Сеньке шапка. Не стоило браться за эту работу с такими вводными, с текущими знаниями Си.

Мне кажется вы бы не выучили Си, если бы вас после каждого совета посылали в центр занятости.

Сделать это легко.

Вам бы определиться.

windows10 ★★★★★
() автор топика
Ответ на: комментарий от windows10

Предпологаю это усложняет компиляцию-оптимизацию, поэтому думаю что нельзя.
Я не знаю C тоже, но сделал бы несколько функций для разных типов данных, с передачей в них без модификаций транзитом «data» без объявления, с локалным объявлением в этих функциях data, пропустив перед этим аргументы через функцию проверки на тип данных в которой нет чтения данных. Сложно описать, крч функция проверяющая тип -> разные функции работающие с данными в которых data локальный.

fucpsy
()
Последнее исправление: fucpsy (всего исправлений: 3)