Как правильно использовать пустой указатель в C? - PullRequest
6 голосов
/ 09 декабря 2011

Может кто-нибудь объяснить, почему я не получаю значение переменной, а вместо этого ее память?

Мне нужно использовать void *, чтобы указывать на "беззнаковые короткие" значения.Я понимаю пустые указатели, их размер неизвестен, а их тип неизвестен.
Однако, как только они инициализируются, они известны, верно?

Почему мой оператор printf выводит неправильное значение?*

На выходе я получаю:

res = 30
b =30
result = 30
res = 90
b =90
result = 44974

Ответы [ 8 ]

8 голосов
/ 09 декабря 2011

Следует помнить одну вещь: C не гарантирует, что int будет достаточно большим, чтобы содержать указатель (включая void*).Этот актерский состав - , а не - портативная вещь / хорошая идея.Используйте %p для printf указатель.

Аналогично, вы выполняете здесь «плохое приведение»: void* res = (int*) a говорит компилятору: «Я уверен, что значение aдействительный int*, поэтому вы должны относиться к нему как таковому. "Если вы на самом деле не знаете, что по адресу памяти 30 хранится int, это неправильно.

К счастью, вы немедленно перезаписываете res адресом другого a.(У вас есть две переменные с именем a и две с именем res, те, что в main, и те, что в func. Те, что в func, являются копиями значения той, которая в main, когдаВы называете это там.) Вообще говоря, перезаписывать значение параметра в функцию - это «плохая форма», но технически это допустимо.Лично я рекомендую объявлять параметры всех ваших функций как const 99% времени (например, void func (const int a, const void* res))

Затем вы преобразуете res в unsigned short.Я не думаю, что кто-то все еще работает на 16-битном процессоре адресного пространства (ну, может быть, на вашем Apple II, может быть), так что определенно повредит значение res путем его усечения.

Как правило, в Си типы типов опасны. Вы отвергаете систему типов компилятора и говорите: «Послушайте, мистер Компилятор, я программист, и я знаю лучше, чемты, что у меня есть здесь. Так что, просто молчи и сделай так, чтобы это произошло. "Преобразование из указателя в тип без указателя почти всегда неверно.Преобразование между типами указателей чаще всего неправильно, чем нет.

Я бы посоветовал проверить некоторые ссылки "Связанные" на этой странице, чтобы найти хороший обзор того, как типы C работают с указателями в целом.Иногда для того, чтобы по-настоящему понять, как все это сочетается, нужно прочитать несколько человек.

6 голосов
/ 09 декабря 2011
(unsigned short)res

- приведение к указателю, res - адрес памяти, приведя его к беззнаковому короткому значению, вы получите значение адреса как беззнаковое короткое, а не шестнадцатеричное значение, чтобы быть уверенным, что вы получитеправильное значение, которое вы можете напечатать

*(unsigned short*)res

При первом приведении (unsigned short*)res приведение void* приводит к указателю на unsigned short.Затем вы можете извлечь значение из адреса памяти, на который указывает указатель, разыменовав его, используя *

1 голос
/ 20 ноября 2015

Если вы хотите передать переменную a по имени и использовать ее, попробуйте что-то вроде:

void func(int* src)
{
  printf( "%d\n", *src );
}

Если вы получили void* из библиотечной функции, и вы знаете,фактический тип, вы должны немедленно сохранить его в переменной правильного типа:

int *ap = calloc( 1, sizeof(int) );

Есть несколько ситуаций, в которых вы должны получить параметр по ссылке как void* и затем привести его.Тот, с которым я сталкиваюсь чаще всего в реальном мире, является процедурой потока.Таким образом, вы можете написать что-то вроде:

#include <stddef.h>
#include <stdio.h>

#include <pthread.h>

void* thread_proc( void* arg )
{
  const int a = *(int*)arg;
 /** Alternatively, with no explicit casts:
  * const int* const p = arg;
  * const int a = *p;
  */
  printf( "Daughter thread: %d\n", a );
  fflush(stdout); /* If more than one thread outputs, should be atomic. */
  return NULL;
}

int main(void)
{
  int a = 1;
  const pthread_t tid = pthread_create( thread_proc, &a );
  pthread_join(tid, NULL);

  return EXIT_SUCCESS;
}

Если вы хотите жить опасно, вы можете передать значение uintptr_t на void* и привести его обратно, но остерегайтесь представлений ловушек.

1 голос
/ 29 апреля 2014

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

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

У большей части старого источника C есть приведения в стиле C между указателями типов и указателями void.В современных компиляторах этого нет необходимости, и его следует избегать.

Размер переменной void-указателя известен.Что не известно, так это размер переменной, указатель которой находится в переменной void pointer.Например, вот несколько исходных примеров.

// create several different kinds of variables
int iValue;
char aszString[6];
float  fValue;

int  *pIvalue = &iValue;
void *pVoid = 0;

int iSize = sizeof(*pIvalue);   // get size of what int pointer points to, an int

int vSize = sizeof(*pVoid);     // compile error, size of what void pointer points to is unknown
int vSizeVar = sizeof(pVoid);   // compiles fine size of void pointer is known

pVoid = &iValue;        // put the address of iValue into the void pointer variable
pVoid = &aszString[0];  // put the address of char string into the void pointer variable
pVoid = &fValue;        // put the address of float into the void pointer variable

pIvalue = &fValue;      // compiler error, address of float into int pointer not allowed

Один из способов использования указателей void - использование нескольких различных типов структур, которые предоставляются в качестве аргумента для функции, обычно это своего рода диспетчерская функция.,Поскольку интерфейс для функции допускает разные типы указателей, в списке аргументов должен использоваться указатель void.Затем тип указанной переменной определяется либо дополнительным аргументом, либо проверкой указанной переменной.Примером такого использования функции может быть что-то вроде следующего.В этом случае мы включаем индикатор типа структуры в первый член различных перестановок структуры.Пока все структуры, которые используются с этой функцией, имеют в качестве первого члена int, указывающий тип структуры, это будет работать.

struct struct_1 {
    int iClass;  // struct type indicator.  must always be first member of struct
    int iValue;
};

struct struct_2 {
    int   iClass;  // struct type indicator.  must always be first member of struct
    float fValue;
};

void func2 (void *pStruct)
{
    struct struct_1 *pStruct_1 = pStruct;
    struct struct_2 *pStruct_2 = pStruct;

    switch (pStruct_1->iClass)  // this works because a struct is a kind of template or pattern for a memory location
    {
    case 1:
        // do things with pStruct_1
        break;
    case 2:
        // do things with pStruct_2
        break;
    default:
        break;
    }
}

void xfunc (void)
{
    struct struct_1 myStruct_1 = {1, 37};
    struct struct_2 myStruct_2 = {2, 755.37f};

    func2 (&myStruct_1);
    func2 (&myStruct_2);
}

Что-то подобное выше имеет ряд проблем проектирования программного обеспечения.с помощью сцепления и сцепления, поэтому, если у вас нет веских причин для использования этого подхода, лучше переосмыслить свой дизайн.Однако язык программирования C позволяет вам это делать.

В некоторых случаях необходим указатель void.Например, функция malloc(), которая выделяет память, возвращает указатель void, содержащий адрес области, которая была выделена (или NULL, если выделение не удалось).В этом случае указатель void допускает одну функцию malloc(), которая может возвращать адрес памяти для любого типа переменной.Ниже показано использование malloc() с различными типами переменных.

void yfunc (void)
{
    int *pIvalue = malloc(sizeof(int));
    char *paszStr = malloc(sizeof(char)*32);

    struct struct_1 *pStruct_1 = malloc (sizeof(*pStruct_1));
    struct struct_2 *pStruct_2Array = malloc (sizeof(*pStruct_2Array)*21);

    pStruct_1->iClass = 1; pStruct_1->iValue = 23;

    func2(pStruct_1);  // pStruct_1 is already a pointer so address of is not used

    {
        int i;
        for (i = 0; i < 21; i++) {
            pStruct_2Array[i].iClass = 2;
            pStruct_2Array[i].fValue = 123.33f;
            func2 (&pStruct_2Array[i]);  // address of particular array element.  could also use func2 (pStruct_2Array + i)
        }
    }
    free(pStruct_1);
    free(pStruct_2Array);   // free the entire array which was allocated with single malloc()
    free(pIvalue);
    free(paszStr);
}
0 голосов
/ 09 декабря 2011

Размер пустого указателя известен; это размер адреса, такой же, как у любого другого указателя. Вы свободно конвертируете между целым числом и указателем, и это опасно. Если вы хотите взять адрес переменной a, вам нужно преобразовать ее адрес в void * с помощью (void *)&a.

0 голосов
/ 09 декабря 2011
void *res = (int *)a;

a является int, но не ptr, может быть, это должно быть:

void *res = &a;
0 голосов
/ 09 декабря 2011

Если у вас есть пустой указатель ptr, который, как вы знаете, указывает на int, для доступа к этому int напишите:

int i = *(int*)ptr;

То есть сначала приведите его к указателю на int с помощью оператора приведения (int*), а затем разыменуйте его, чтобы получить указанное значение.

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

0 голосов
/ 09 декабря 2011

printf("result = %d\n", (int)res); печатает значение res (указатель) в виде числа.Помните, что указатель - это адрес в памяти, поэтому он напечатает какое-то случайное 32-битное число.

Если вы хотите напечатать значение, хранящееся по этому адресу, вам нужно (int) * res - хотя (int) не нужно.

edit: если вы хотите напечатать значение (то есть адрес) указателя, тогда вы должны использовать %p, по сути, то же самое, но форматирует его лучше и понимает, если размер int и poitner различаются на вашей платформе

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...