Я хотел бы использовать это для реализации таких вещей, как передача в объект значения ошибки NULL, и, если есть ошибка, он устанавливает указатель ошибки на некоторый код ошибки и т. Д.
Из приведенной выше цитаты и из кода в вопросе кажется, что вы ищете переменную, которая может "содержать" различные типы, то есть иногда вы хотите, чтобы она была целым числом, в других случаях - с плавающей точкой,в других случаях строка и так далее.Это называется вариант в некоторых языках, но вариантов не существует в C . (см. Это https://en.wikipedia.org/wiki/Variant_type для получения дополнительной информации о вариантах)
Так что в C вам придется кодировать свой собственный тип варианта.Есть несколько способов сделать это.Ниже я приведу примеры.
Но сначала несколько слов об указателях на C, потому что код в вопросе, кажется, обнаруживает недопонимание, так как присваивает значения непосредственно указателю, например, pointer = somestruct;
, что недопустимо.
В C очень важно понимать разницу между «значением указателя» и «значением указателя на объект».Первый, то есть значение указателя, сообщает, куда указывает указатель, то есть значение указателя является адресом указанного объекта.Назначение указателя изменяется, где указатель указывает.Чтобы изменить значение указанного объекта, указатель должен быть сначала разыменован.Пример (псевдокод):
pointer = &some_int; // Make pointer point to some_int
*pointer = 10; // Change the value of the pointed to object, i.e. some_int
// Notice the * in front of pointer - it's the dereference
// that tells you want to operate on the "pointed to object"
pointer = 10; // Change the value of the pointer, i.e. where it points to
// In other words, pointer no longer points to some_int
Теперь вернемся к реализации "варианта".Как уже упоминалось, есть несколько способов закодировать это в C.
Из вашего вопроса кажется, что вы хотите использовать void-указатель.Это выполнимо, и я начну с показа примера с использованием void-pointer, а после этого примера с использованием объединения.
В вашем вопросе неясно, что такое cond
, поэтому в своих примерах я просто предположуэто аргумент командной строки, и я просто добавил некоторую интерпретацию, чтобы получить работающий пример.
Обычным примером для примеров является использование «тега».Это дополнительная переменная, которая сообщает текущий тип значения объекта (он же метаданные).Таким образом, тип данных общего варианта выглядит следующим образом:
struct my_variant
{
TagType tag; // Tells the current type of the value object
ValueType value; // The actual value. ValueType is a type that allows
// storing different object types, e.g. a void-pointer or a union
}
Пример 1: указатель void и приведение
В приведенном ниже примере будет использоваться указатель void для указания наобъект, содержащий реальную стоимость.Значение, которое иногда представляет собой целое число, иногда число с плавающей запятой или все, что нужно.При работе с указателем void необходимо привести указатель void перед разыменованием указателя (т. Е. Перед доступом к указанному объекту).Поле tag
сообщает тип указанного объекта и, следовательно, также, каким должно быть приведение.
#include <stdio.h>
#include <stdlib.h>
// This is the TAG type.
// To keep the example short it only has int and float but more can
// be added using the same pattern
typedef enum
{
INT_ERROR_TYPE,
FLOAT_ERROR_TYPE,
UNKNOWN_ERROR_TYPE,
} error_type_e;
// This is the variant type
typedef struct
{
error_type_e tag; // The tag tells the type of the object pointed to by value_ptr
void* value_ptr; // void pointer to error value
} error_object_t;
// This function evaluates the error and (if needed)
// creates an error object (i.e. the variant) and
// assigns appropriate values of different types
error_object_t* get_error_object(int err)
{
if (err >= 0)
{
// No error
return NULL;
}
// Allocate the variant
error_object_t* result_ptr = malloc(sizeof *result_ptr);
// Set tag value
// Allocate value object
// Set value of value object
if (err > -100) // -99 .. -1 is INT error type
{
result_ptr->tag = INT_ERROR_TYPE;
result_ptr->value_ptr = malloc(sizeof(int));
*(int*)result_ptr->value_ptr = 42;
}
else if (err > -200) // -199 .. -100 is FLOAT error type
{
result_ptr->tag = FLOAT_ERROR_TYPE;
result_ptr->value_ptr = malloc(sizeof(float));
*(float*)result_ptr->value_ptr = 42.42;
}
else
{
result_ptr->tag = UNKNOWN_ERROR_TYPE;
result_ptr->value_ptr = NULL;
}
return result_ptr;
}
int main(int argc, char* argv[])
{
if (argc < 2) {printf("Missing arg\n"); exit(1);}
int err = atoi(argv[1]); // Convert cmd line arg to int
error_object_t* err_ptr = get_error_object(err);
if (err_ptr == NULL)
{
// No error
// ... add "normal" code here - for now just print a message
printf("No error\n");
}
else
{
// Error
// ... add error handler here - for now just print a message
switch(err_ptr->tag)
{
case INT_ERROR_TYPE:
printf("Error type INT, value %d\n", *(int*)err_ptr->value_ptr);
break;
case FLOAT_ERROR_TYPE:
printf("Error type FLOAT, value %f\n", *(float*)err_ptr->value_ptr);
break;
default:
printf("Error type UNKNOWN, no value to print\n");
break;
}
free(err_ptr->value_ptr);
free(err_ptr);
}
return 0;
}
Некоторые примеры запуска этой программы:
> ./prog 5
No error
> ./prog -5
Error type INT, value 42
> ./prog -105
Error type FLOAT, value 42.419998
> ./prog -205
Error type UNKNOWN, no value to print
В качестве примеракак показано выше, вы можете реализовать тип варианта, используя void-pointer.Тем не менее, код требует много преобразования, что затрудняет чтение кода.В общем, я не буду рекомендовать этот подход, если у вас нет особых требований, которые заставляют использовать void-pointer.
Пример 2: указатель на объединение
Как объясненоранее C не имеет вариантов, так как они известны на других языках.Тем не менее, C имеет кое-что, что довольно близко.Это союзов .Объединение может содержать разные типы в разное время - все, что ему не хватает, это tag
.Поэтому вместо использования тега и void-указателя вы можете использовать тег и объединение.Преимущество состоит в том, что 1) кастинг не понадобится и 2) избегается malloc
.Пример:
#include <stdio.h>
#include <stdlib.h>
typedef enum
{
INT_ERROR_TYPE,
FLOAT_ERROR_TYPE,
UNKNOWN_ERROR_TYPE,
} error_type_e;
// The union that can hold an int or a float as needed
typedef union
{
int n;
float f;
} error_union_t;
typedef struct
{
error_type_e tag; // The tag tells the current union use
error_union_t value; // Union of error values
} error_object_t;
error_object_t* get_error_object(int err)
{
if (err >= 0)
{
// No error
return NULL;
}
error_object_t* result_ptr = malloc(sizeof *result_ptr);
if (err > -100) // -99 .. -1 is INT error type
{
result_ptr->tag = INT_ERROR_TYPE;
result_ptr->value.n = 42;
}
else if (err > -200) // -199 .. -100 is FLOAT error type
{
result_ptr->tag = FLOAT_ERROR_TYPE;
result_ptr->value.f = 42.42;
}
else
{
result_ptr->tag = UNKNOWN_ERROR_TYPE;
}
return result_ptr;
}
int main(int argc, char* argv[])
{
if (argc < 2) {printf("Missing arg\n"); exit(1);}
int err = atoi(argv[1]); // Convert cmd line arg to int
error_object_t* err_ptr = get_error_object(err);
if (err_ptr == NULL)
{
// No error
// ... add "normal" code here - for now just print a message
printf("No error\n");
}
else
{
// Error
// ... add error handler here - for now just print a message
switch(err_ptr->tag)
{
case INT_ERROR_TYPE:
printf("Error type INT, value %d\n", err_ptr->value.n);
break;
case FLOAT_ERROR_TYPE:
printf("Error type FLOAT, value %f\n", err_ptr->value.f);
break;
default:
printf("Error type UNKNOWN, no value to print\n");
break;
}
free(err_ptr);
}
return 0;
}
На мой взгляд, этот код легче читать, чем код, использующий void-pointer.
Пример 3: объединение - нет указателя - нет malloc
Даже если пример 2 лучше, чем пример 1, в примере 2. динамическое распределение памяти все еще сохраняется. Динамическое выделение является частью большинства программ на Си, но оно должно использоваться только тогда, когда это действительно необходимо.Другими словами - объекты с автоматической продолжительностью хранения (или локальные переменные) должны быть предпочтительнее динамически выделенных объектов, когда это возможно.
В приведенном ниже примере показано, как избежать динамического выделения.
#include <stdio.h>
#include <stdlib.h>
typedef enum
{
NO_ERROR,
INT_ERROR_TYPE,
FLOAT_ERROR_TYPE,
UNKNOWN_ERROR_TYPE,
} error_type_e;
typedef union
{
int n;
float f;
} error_union_t;
typedef struct
{
error_type_e tag; // The tag tells the current union usevalue_ptr
error_union_t value; // Union of error values
} error_object_t;
error_object_t get_error_object(int err)
{
error_object_t result_obj;
if (err >= 0)
{
// No error
result_obj.tag = NO_ERROR;
}
else if (err > -100) // -99 .. -1 is INT error type
{
result_obj.tag = INT_ERROR_TYPE;
result_obj.value.n = 42;
}
else if (err > -200) // -199 .. -100 is FLOAT error type
{
result_obj.tag = FLOAT_ERROR_TYPE;
result_obj.value.f = 42.42;
}
else
{
result_obj.tag = UNKNOWN_ERROR_TYPE;
}
return result_obj;
}
int main(int argc, char* argv[])
{
if (argc < 2) {printf("Missing arg\n"); exit(1);}
int err = atoi(argv[1]); // Convert cmd line arg to int
error_object_t err_obj = get_error_object(err);
switch(err_obj.tag)
{
case NO_ERROR:
printf("No error\n");
break;
case INT_ERROR_TYPE:
printf("Error type INT, value %d\n", err_obj.value.n);
break;
case FLOAT_ERROR_TYPE:
printf("Error type FLOAT, value %f\n", err_obj.value.f);
break;
default:
printf("Error type UNKNOWN, no value to print\n");
break;
}
return 0;
}
Сводка
Существует множество способов решения проблемы, решаемой с помощью OP.В этом ответе приведены три примера.На мой взгляд, пример 3 - лучший подход, поскольку он избегает динамического выделения памяти и указателей, но могут быть ситуации, когда пример 1 или 2 лучше.