如何在C

时间:2019-03-23 06:18:24

标签: c function pointers void-pointers

我是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错误对象传递给函数的功能,并且如果存在错误,它将错误的指针设置为某些错误代码,等等。

我认为这不是一个广泛的问题,但是如果是这样,那么知道在哪里寻找源代码中更详尽的解释或示例会有所帮助。

7 个答案:

答案 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(&current_int, myIntQueue);
    get_from_queue(&current_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(&current) == 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(以下评论):好吧,我想如果您不知道要写入的对象类型,就无法进行分配。对我来说似乎是矛盾的...