我是C语言的新手,想知道如何做一些pointer stuff。特别是在这里,我想知道如何将指针传递到函数中并“从函数中获取值”。有点像这样(半伪代码):
assign_value_to_pointer(void* pointer) {
if (cond1) {
pointer = 10;
} else if (cond2) {
pointer = "foo";
} else if (cond3) {
pointer = true;
} else if (cond4) {
pointer = somestruct;
} else if (cond5) {
pointer = NULL;
} else if (cond6) {
// unknown type!
pointer = flexiblearraymember.items[index];
}
}
main() {
void* pointer = NULL;
assign_value_to_pointer(&pointer);
if (cond1) {
assert(pointer == 10);
} else if (cond2) {
assert(pointer == "foo");
} else if (cond3) {
assert(pointer == true);
} else if (cond4) {
assert(pointer == somestruct);
} else if (cond5) {
assert(pointer == NULL);
}
}
采用另一种方式:
p = new Pointer()
assign_a_value(p)
assert(p.value == 10) // or whatever
基本上是将指针传递到函数中,该函数正在为指针分配一个值,然后您可以在返回时在函数外部使用该值。您可能不知道从函数中获得什么样的值(但是可以通过将其扩展为使用结构等来处理),因此就知道了void指针。不过,主要目标只是将指针传递到某个函数中,并使其吸收一些值。
通过快速的示例实现思考如何在C中正确执行此操作。不必仅仅为了开始就涵盖所有情况。
我想用它来实现诸如将NULL错误对象传递给函数的功能,并且如果存在错误,它将错误的指针设置为某些错误代码,等等。
我认为这不是一个广泛的问题,但是如果是这样,那么知道在哪里寻找源代码中更详尽的解释或示例会有所帮助。
答案 0 :(得分:3)
首先,我将直接回答您的问题,希望您理解为什么需要重新谨慎。这对于实现队列或通信堆栈可能是一种有用的技术-但是您需要确定可以重新获得存储哪些类型的跟踪,否则程序逻辑将完全中断。然后,我将尝试简要介绍一些用例和使其变得安全的一些方法。
一个简单的例子,完全按照您说的做
#include <stdio.h>
#include <stdlib.h>
//Some basic error type for reporting failures
typedef enum my_error
{
ERROR_NONE = 0,
ERROR_FAIL = 1,
} my_error;
struct my_struct
{
int age;
char *name;
int order_count;
};
int someCond = 1;
//Let's start with a simple case, where we know the type of the pointer being passed (an int)
//If int_out is NULL, then this function will invoke undefined behavior (probably a
//runtime crash, but don't rely on it).
my_error assign_int(int *int_out)
{
if(someCond)
*int_out = 5;
else
*int_out = 38;
return ERROR_NONE;
}
//Need to use a 'double pointer', so that this function is actually changing the pointer
//that exists in the parent scope
my_error dynamically_assign_value_to_pointer(void **pointer)
{
//A pointer internal to this function just to simplify syntax
void *working_ptr = NULL;
if(someCond)
{
//Allocate a region of memory, and store its location in working_ptr
working_ptr = malloc(sizeof(int));
//store the value 12 at the location that working_ptr points to (using '*' to dereference)
*((int *) working_ptr) = 12;
}
else
{
//Allocate a region of memory, and store its location in working_ptr
working_ptr = malloc(sizeof(struct my_struct));
//Fill the struct with data by casting (You can't dereference a void pointer,
//as the compiler doesn't know what it is.)
((struct my_struct *) working_ptr)->age = 22;
((struct my_struct *) working_ptr)->name = "Peter";
((struct my_struct *) working_ptr)->order_count = 6;
}
//Set the pointer passed as an argument to point to this data, by setting the
//once-dereferenced value
*pointer = working_ptr;
return ERROR_NONE;
}
int main (int argc, char *argv[])
{
int an_int;
void *some_data;
assign_int(&an_int);
//an_int is now either 5 or 38
dynamically_assign_value_to_pointer(&some_data);
//some_data now points to either an integer OR a my_struct instance. You will need
//some way to track this, otherwise the data is useless.
//If you get this wrong, the data will be interpreted as the wrong type, and the
//severity of the issue depends what you do with it.
//For instance, if you KNOW FOR SURE that the pointer contains the int, you could
//print it by:
printf("%d", *((int *) some_data));
//And because it is dynamically allocated, you MUST free it.
free(some_data);
return 0;
}
例如,在实践中,这对队列很有用,因此您可以编写通用队列函数,然后为不同的数据类型使用不同的队列。这是部分代码,因此不会编译,并且在这种有限的情况下是个坏主意,因为在设计类型安全的替代方法时,微不足道的是微不足道的,但是希望您能理解:
extern my_queue_type myIntQueue;
extern my_queue_type myStructQueue;
my_error get_from_queue(void *data_out, my_queue_type queue_in);
int main (int argc, char *argv[])
{
//...
int current_int;
struct my_struct current_struct;
get_from_queue(¤t_int, myIntQueue);
get_from_queue(¤t_struct, myStructQueue);
//...
}
或者,如果您真的想将许多不同类型存储在一起,则至少应在结构中跟踪类型以及指针,以便可以使用“开关”来在必要时适当地转换和处理逻辑。同样,部分示例因此无法编译。
enum my_types
{
MY_INTEGER, MY_DOUBLE, MY_STRUCT
};
struct my_typed_void
{
void *data;
enum my_types datatype;
};
my_error get_dynamic_from_global_queue(struct my_typed_void *data_out)
{
//...
data_out->data = malloc(sizeof int);
*((int *)(data_out->data)) = 33;
data_out->datatype = MY_INTEGER;
//...
}
int main (int argc, char *argv[])
{
struct my_typed_void current;
if(get_dynamic_from_global_queue(¤t) == ERROR_NONE)
{
switch(current.datatype)
{
//...
case MY_INTEGER:
printf("%d", *((int *) current.data));
break;
//...
}
free(current.data);
}
return 0;
}
答案 1 :(得分:0)
返回指针或将指针传递给指针(然后函数将更改指针):
void* f1(void* p)
{
p = whatever(p, conditions);
return p;
}
void f2(void** p)
{
*p = whatever(*p, conditions);
}
答案 2 :(得分:0)
void assign_value_to_pointer(int** pointer) {
**pointer = 20;
}
void main() {
void* pointer = NULL;
pointer=malloc(sizeof(int));
*(int *)pointer=10;
assign_value_to_pointer(&pointer);
}
答案 3 :(得分:0)
我不确定100%是否在寻找什么,但这可能是这样的:
enum pointer_type{INTEGER, STRUCTURE_1, STRUCTURE_2, INVALID};
int assign_value_to_pointer(void ** ptr)
{
uint8_t cond = getCondition();
switch(cond)
{
case 1:
*ptr = (void*) 10;
return INTEGER;
case 2:
*ptr = (void*) someStructOfType1;
return STRUCTURE_1;
case 3:
*ptr = (void*) someStructOfType2;
return STRUCTURE_2;
default:
*ptr = NULL;
return INVALID;
};
}
void main(void)
{
void * ptr = NULL;
int ptrType = assign_value_to_pointer(&ptr);
switch(ptrType)
{
case INTEGER:
assert(ptr == (void*)10);
break;
case STRUCTURE_1:
assert( ((structType1*) ptr)->thing == something);
break;
case STRUCTURE_2:
assert( ((structType2*) ptr)->something == something);
break;
default:
assert(ptr == NULL);
}
}
答案 4 :(得分:0)
您实际上可以根据情况(条件)和用途在main()中键入类型转换指针。但是,我认为您可以为此目的使用联合。
使用所有可能的数据类型创建联合。
typedef union _my_union_type_ {
int intVal;
char* stringVal;
bool boolVal;
SomestructType somestruct;//Assuming you need a structure not structure pointer.
void* voidPtrType;
} my_union_type;
现在在main()
中,创建这种联合类型的变量,并将联合的地址传递给函数。
main() {
my_union_type my_union;
memset(&my_union, 0x00, sizeof(my_union));
assign_value_to_pointer(&my_union);
if (cond1) {
assert(my_union.intVal == 10);
} else if (cond2) {
assert(strcmp(my_union.stringVal, "foo")); //String comparison can not be done using '=='
} else if (cond3) {
assert(my_union.boolVal == true);
} else if (cond4) {
assert(memcmp(&my_union.somestruct, &somestruct, sizeof(somestruct)); //Assuming structure not structure pointer.
} else if (cond5) {
assert(my_union.voidPtrType == NULL);
} else if (cond5) {
//Check my_union.voidPtrType
}
}
然后,您可以在Assign_value_to_pointer中将所需的值存储在并集变量中。
assign_value_to_pointer(my_union_type* my_union) {
if (cond1) {
my_union->intVal = 10;
} else if (cond2) {
my_union->stringVal = "foo";
} else if (cond3) {
my_union->boolVal = true;
} else if (cond4) {
memcpy(&(my_union->somestruct), &somestruct, sizeof(somestruct));
} else if (cond5) {
my_union->voidPtrType = NULL;
} else if (cond6) {
// unknown type!
my_union->voidPtrType = flexiblearraymember.items[index];
}
}
答案 5 :(得分:0)
我想用它来实现诸如将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-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:无效指针和强制转换
下面的示例将使用一个空指针指向包含实数值的对象。该值有时是整数,有时是浮点数或任何需要的值。使用空指针时,有必要在取消引用指针之前(即在访问指向对象之前)强制转换空指针。 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指针实现变量类型。但是,代码需要大量的转换,这使得代码难以阅读。通常,除非您有一些强制使用空指针的特殊要求,否则我不建议您使用这种方法。
示例2:指向联合的指针
如前所述,C没有其他语言中已知的变体。但是,C具有非常接近的东西。那是联盟。一个联合可以在不同的时间拥有不同的类型-它所缺的只是一个tag
。因此,可以使用标签和联合来代替使用标签和空指针。好处是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;
}
我认为,此代码比使用空指针的代码更易于阅读。
示例3:联合-无指针-无malloc
即使示例2比示例1更好,示例2中仍然有动态内存分配。动态分配是大多数C程序的一部分,但只有在真正需要时才使用它。换句话说,在可能的情况下,应优先选择具有自动存储时间(即本地变量)的对象,而不是动态分配的对象。
下面的示例显示了如何避免动态分配。
#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更好。
答案 6 :(得分:-1)
您离成功并不遥远,只是错过了一个星号来取消引用参数:
void assign_value_to_pointer(void* pointer) {
if (cond1) {
*pointer = 10; // note the asterisk
...
}
void main() {
void* pointer = NULL;
assign_value_to_pointer(&pointer);
}
在C语言中,函数的参数始终按值传递。如果要让函数修改参数,则必须传递要修改的变量的地址。在main()中,您正在这样做-正确。被调用函数可以写出其参数指向的位置,从而修改原始变量。为此,必须取消引用参数。
编译器应该对分配感到生气,因为它不知道要写入多少字节(我一直保持简单)。因此,您必须说出指针所指向的对象是什么,像这样:
*(int *) pointer = 10;
您选择的类型转换取决于您,具体取决于上下文。
这时...为什么不以不同的方式声明函数:
void assign_value_to_pointer(int* pointer) {
if (cond1) {
*pointer = 10; // note the asterisk
}
现在,类型转换不再是必需的,因为编译器知道对象的种类(再次使它保持简单-void
非常特殊)。
*******评论后编辑
好吧,我不是C语言专家,此外,我想保持低调,以更好地帮助OP。
对于简单的情况,正确的声明是幼稚的。类型转换可以更加灵活,因为函数可以根据上下文从几个赋值语句中进行选择。最后,如果函数传递了指针和其他参数,则一切皆有可能,包括使用memcpy()。但这最后一个解决方案开辟了一个世界...
回复Lance(以下评论):好吧,我想如果您不知道要写入的对象类型,就无法进行分配。对我来说似乎是矛盾的...