Как вообще назначить указатель, переданный в функцию в C - PullRequest
0 голосов
/ 23 марта 2019

Я новичок в C, и мне интересно, как сделать указатель .В частности, здесь мне интересно, как вы можете передать указатель на функцию и «получить значение из функции».Вроде как (полупсевдокод):

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 ]

3 голосов
/ 13 апреля 2019

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

Простой пример того, что вы сказали

#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);

    //...
}

Или, если вы действительно хотите хранить много разных типов вместе, вы должны, по крайней мере, отслеживать тип вместе с указателем в структуре, чтобы вы могли использовать 'switch' для того, чтобы при необходимости соответствующим образом приводить и обрабатывать логику. Опять же, частичный пример, поэтому не скомпилируется.

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;
}
0 голосов
/ 16 апреля 2019

Я хотел бы использовать это для реализации таких вещей, как передача в объект значения ошибки 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 лучше.

0 голосов
/ 15 апреля 2019

Вы можете набрать указатель в 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 вы можете сохранить требуемое значение в переменной union.

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];
  }
}
0 голосов
/ 10 апреля 2019

Я не уверен на 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);
    }
}
0 голосов
/ 23 марта 2019
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);
}
0 голосов
/ 23 марта 2019

Вы не далеки от успеха, вы просто пропускаете звездочку, чтобы разыменовать аргумент:

void assign_value_to_pointer(void* pointer) {
  if (cond1) {
    *pointer = 10;       // note the asterisk
  ...
}

void main() {
  void* pointer = NULL;

  assign_value_to_pointer(&pointer);

}

В языке Си аргументы функций всегда передаются по значению. Если вы хотите, чтобы функция изменила аргумент, вы должны передать адрес переменной, которую хотите изменить. В main () вы делаете это - правильно. Вызываемая функция может написать, куда указывает ее аргумент, следовательно, изменив исходную переменную; чтобы сделать это, вы должны разыменовать аргумент.

Компилятор должен злиться на присваивание, потому что он не знает, сколько байтов записать (я упрощаю). Итак, вы должны сказать, на какой объект указывает указатель, например:

*(int *) pointer = 10;

Выбранный вами тип передачи зависит от вас.

На этом этапе ... почему бы не объявить по-другому функцию:

void assign_value_to_pointer(int* pointer) {
  if (cond1) {
    *pointer = 10;       // note the asterisk
}

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

******* РЕДАКТИРОВАТЬ после комментариев

Ну, я не гуру в языке Си и, кроме того, я хотел вести себя сдержанно, чтобы лучше помогать ОП.

Для простых случаев правильное объявление наивно. Типовое преобразование может быть более гибким, потому что функция может иметь несколько операторов присваивания на выбор в зависимости от контекста. Наконец, если функции передаются указатель и какой-то другой параметр, все возможно, включая использование memcpy (). Но это последнее решение открывает мир ...

Чтобы ответить Лансу (комментарий ниже): ну, я думаю, что нет способа выполнить присваивание , если вы не знаете тип объекта, которому пишете. Мне кажется, что это является контрактом ...

0 голосов
/ 23 марта 2019

Либо верните указатель, либо передайте указатель на указатель (функция затем изменит указатель):

void* f1(void* p)
{
  p = whatever(p, conditions);
  return p;
}

void f2(void** p)
{
  *p = whatever(*p, conditions);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...