如何在C中正确使用void指针?

时间:2011-12-09 16:41:41

标签: c void-pointers

有人可以解释为什么我没有得到变量的值,而是得到它的内存吗?

我需要使用void *来指向“unsigned short”值。

据我了解无效指针,它们的大小未知且类型未知 然而,一旦初始化它们,它们就是已知的,对吧?

为什么我的printf语句打印错误的值?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void func(int a,  void *res){
    res = &a;
    printf("res = %d\n", *(int*)res);
    int b;
    b = * (int *) res;
    printf("b =%d\n", b);
}


int main (int argc, char* argv[])
{
    //trial 1
    int a = 30;
    void *res = (int *)a;
    func(a, res);
    printf("result = %d\n", (int)res);
    //trial 2
    unsigned short i = 90;
    res = &i;
    func(i, res);
    printf("result = %d\n", (unsigned short)res);
    return 0;
}

我得到的输出:

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

8 个答案:

答案 0 :(得分:8)

要记住一件事: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投射到res。我不认为任何人仍然在16位地址空间CPU上运行(好吧,你的Apple II,也许),因此肯定会通过截断它来破坏{{1}}的值

一般来说,在C中,类型转换是危险的。你正在推翻编译器的类型系统,并说:“看看这里,编译先生,我是程序员,我知道比你在这里拥有的东西。所以,你只要保持安静就能实现这一目标。“从指针转换为非指针类型几乎普遍存在错误。指针类型之间的转换通常是错误的。

我建议查看本页下面的一些“相关”链接,以便总体上了解C类型指针的工作原理。有时需要阅读一些才能真正掌握这些东西如何结合在一起。

答案 1 :(得分:6)

(unsigned short)res

是对指针的强制转换,res是一个内存地址,通过将其转换为无符号的short,您将地址值作为无符号的short而不是十六进制值,以确保您将获得正确的值你可以打印

*(unsigned short*)res

第一个演员(unsigned short*)res在指向void*上的指针的unsigned short指针上进行投射。然后,您可以使用*

取消引用res指向的内存地址中的值。

答案 2 :(得分:1)

在C中使用void指针作为一种通用指针。 void指针变量可用于包含任何变量类型的地址。 void指针的问题是,一旦为指针分配了一个地址,编译器就无法再检查有关变量类型的信息。

通常,应避免使用void指针,因为编译器不再可以使用其地址位于void指针中的变量的类型。另一方面,存在无效指针非常方便的情况。但是,程序员需要知道其地址在void指针变量中的变量类型并正确使用它。

许多较旧的C源代码在类型指针和void指针之间都有C样式转换。现代编译器不需要这样做,应该避免使用。

void指针变量的大小是已知的。不知道的是指针在void指针变量中的变量的大小。例如,这里有一些源代码示例。

// 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编程语言允许您这样做。

在某些情况下,空指针是必需的。例如,分配内存的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);
}

答案 3 :(得分:1)

如果您要执行的操作是按名称传递变量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*并将其强制转换,但要注意陷阱表示。

答案 4 :(得分:0)

printf("result = %d\n", (int)res);将res(指针)的值打印为数字。 请记住,指针是内存中的地址,因此这将打印一些随机查看的32位数字。

如果你想打印存储在该地址的值,那么你需要(int)* res - 尽管(int)是不必要的。

编辑:如果你想打印一个指针的值(即地址),那么你应该使用%p它本质上是相同的但是更好地格式化它并且理解int和poitner的大小是否不同你的平台

答案 5 :(得分:0)

如果你有一个空洞指针ptr,你知道指向int,为了访问int写:

int i = *(int*)ptr;

也就是说,首先将它转换为带有强制转换运算符(int*)的指向int的指针,然后取消引用它以获取指向的值。

您正在将指针直接转换为值类型,虽然编译器很乐意这样做,但这可能不是您想要的。

答案 6 :(得分:0)

void *res = (int *)a;

a是一个int但不是ptr,也许应该是:

void *res = &a;

答案 7 :(得分:0)

空指针的大小是已知的;它是地址的大小,因此与任何其他指针的大小相同。你可以在整数和指针之间自由转换,这很危险。如果您要使用变量a的地址,则需要将其地址转换为void * (void *)&a