【问题标题】:Concept of void pointer in C programmingC编程中void指针的概念
【发布时间】:2010-10-16 02:34:44
【问题描述】:

在 C 编程语言中是否可以在不进行类型转换的情况下取消引用 void 指针?

另外,有没有什么方法可以泛化一个可以接收指针并将其存储在 void 指针中的函数,通过使用该 void 指针,我们可以创建一个泛化函数吗?

例如:

void abc(void *a, int b)
{
   if(b==1)
      printf("%d",*(int*)a);     // If integer pointer is received
   else if(b==2)
      printf("%c",*(char*)a);     // If character pointer is received
   else if(b==3)
      printf("%f",*(float*)a);     // If float pointer is received
}

我想在不使用 if-else 语句的情况下使这个函数通用 - 这可能吗?

此外,如果有很好的互联网文章解释了空指针的概念,那么如果您能提供 URL 将是有益的。

另外,是否可以使用 void 指针进行指针运算?

【问题讨论】:

  • 对于某些人来说,如果 * 附加到“类型”而不是名称,这可能会更好读。 IE。 a 是指向某个 void(内存漏洞)的指针。因此,在每个打印语句中,我们告诉编译器我们期望a 指向哪种“空洞/空洞”,然后我们从该指针中获取那种值。 (没有研究 LLP32/LP64 困难的一些 size_t 问题;-)

标签: c void-pointers


【解决方案1】:

在 C 编程语言中是否可以在不进行类型转换的情况下取消引用 void 指针...

不,void 表示没有类型,它不是你可以取消引用或分配的东西。

有什么方法可以泛化一个函数,它可以接收指针并将其存储在 void 指针中,通过使用该 void 指针,我们可以创建一个泛化函数..

您不能只是以可移植的方式取消引用它,因为它可能没有正确对齐。这可能是一些架构(如 ARM)的问题,其中指向数据类型的指针必须在数据类型大小的边界处对齐(例如,指向 32 位整数的指针必须在 4 字节边界处对齐才能取消引用)。

例如,从void*读取uint16_t

/* may receive wrong value if ptr is not 2-byte aligned */
uint16_t value = *(uint16_t*)ptr;
/* portable way of reading a little-endian value */
uint16_t value = *(uint8_t*)ptr
                | ((*((uint8_t*)ptr+1))<<8);

另外,是否可以使用 void 指针进行指针运算...

由于指针下方缺乏具体值以及大小,因此无法对 void 的指针进行指针运算。

void* p = ...
void *p2 = p + 1; /* what exactly is the size of void?? */

【讨论】:

  • 除非我记错了,否则您可以通过两种方式安全地取消引用 void*。转换为 char* 始终是可以接受的,如果您知道它指向的原始类型,则可以转换为该类型。 void* 丢失了类型信息,因此必须将其存储在其他地方。
  • 是的,如果你转换成另一种类型,你总是可以取消引用,但如果你转换,你就不能这样做。简而言之,编译器不会知道将哪些汇编指令用于数学运算直到你转换它。
  • 我认为 GCC 对 void * 指针的算术处理与 char * 相同,但它不是标准的,你不应该依赖它。
  • 我不确定我是否遵循。例如,函数用户保证上面列出的 OP 传入类型是正确的。你是说如果我们有一些东西,我们将一个已知类型的值分配给一个指针,将它转换为一个 void 指针,然后将它转换回它可能变得未对齐的原始指针类型?我不明白为什么编译器会像这样破坏你,只是因为你将它们强制转换为一个 void 指针。或者你只是说我们不能相信用户知道他们传递给函数的参数类型?
  • @doliver 我的意思是#2(“我们不能相信用户知道他们传递给函数的参数类型”)。但是重新审视我 6 年前的回答(呃,真的有那么长吗?)我明白你的意思,它并非总是:当原始指针指向正确的类型时,它将已经对齐,因此可以安全地投射而不会出现对齐问题。
【解决方案2】:

在 C 中,void * 可以转换为指向不同类型对象的指针,而无需显式强制转换:

void abc(void *a, int b)
{
    int *test = a;
    /* ... */

不过,这无助于以更通用的方式编写函数。

您不能通过将void * 转换为不同的指针类型来取消引用它,因为取消引用指针正在获取指向对象的值。裸露的 void 不是有效类型,因此无法取消引用 void *

指针算法是关于将指针值更改为sizeof 指向对象的倍数。同样,因为void 不是真正的类型,sizeof(void) 没有意义,所以指针算术在void * 上无效。 (一些实现允许它,使用 char * 的等效指针算法。)

【讨论】:

  • @CharlesBailey 在你的代码中,你写了void abc(void *a, int b)。但是为什么不使用void abc(int *a, int b),因为在你的函数中你最终会定义int *test = a;,它会保存一个变量,而且我没有看到定义另一个变量的任何其他优势。我看到许多以这种方式编写的代码使用void * 作为参数,并在函数的后面转换变量。但是既然这个函数是作者写的,所以他必须知道变量的用法,所以为什么要用void *呢?谢谢
  • 我相信你想说的是“你不能取消引用 void * 没有将其转换为不同的指针类型”
【解决方案3】:

您应该知道,在 C 中,与 Java 或 C# 不同,绝对不可能成功“猜测”void* 指针指向的对象类型。类似于getClass() 的东西根本不存在,因为找不到此信息。出于这个原因,您正在寻找的那种“通用”总是带有明确的元信息,例如您的示例中的 int bprintf 系列函数中的格式字符串。

【讨论】:

    【解决方案4】:

    空指针被称为泛型指针,它可以引用任何数据类型的变量。

    【讨论】:

      【解决方案5】:

      到目前为止,我对 void 指针的理解如下。

      当使用关键字 void 声明指针变量时,它就变成了通用指针变量。任何数据类型(char、int、float 等)的任何变量的地址都可以分配给 void 指针变量。

      main()
      {
          int *p;
      
          void *vp;
      
          vp=p;
      } 
      

      由于其他数据类型的指针可以赋值给void指针,所以我在absolut_value(代码如下)函数中使用了它。做一个通用函数。

      我尝试编写一个简单的 C 代码,它将整数或浮点数作为参数并尝试使其为 +ve,如果为负数。我写了以下代码,

      #include<stdio.h>
      
      void absolute_value ( void *j) // works if used float, obviously it must work but thats not my interest here.
      {
          if ( *j < 0 )
              *j = *j * (-1);
      
      }
      
      int main()
      {
          int i = 40;
          float f = -40;
          printf("print intiger i = %d \n",i);
          printf("print float f = %f \n",f);
          absolute_value(&i);
          absolute_value(&f);
          printf("print intiger i = %d \n",i);
          printf("print float f = %f \n",f);
          return 0;
      }   
      

      但是我遇到了错误,所以我开始知道我对 void 指针的理解是不正确的 :(。所以现在我将继续收集积分为什么会这样。

      关于 void 指针,我需要更多了解的是。

      我们需要对 void 指针变量进行类型转换以取消引用它。这是因为 void 指针没有与之关联的数据类型。编译器无法知道(或猜测?) void 指针指向的数据类型。因此,为了获取 void 指针指向的数据,我们使用 void 指针位置中保存的正确数据类型对其进行类型转换。

      void main()
      
      {
      
          int a=10;
      
          float b=35.75;
      
          void *ptr; // Declaring a void pointer
      
          ptr=&a; // Assigning address of integer to void pointer.
      
          printf("The value of integer variable is= %d",*( (int*) ptr) );// (int*)ptr - is used for type casting. Where as *((int*)ptr) dereferences the typecasted void pointer variable.
      
          ptr=&b; // Assigning address of float to void pointer.
      
          printf("The value of float variable is= %f",*( (float*) ptr) );
      
      }
      

      如果程序员不确定最终用户输入的数据的数据类型,空指针会非常有用。在这种情况下,程序员可以使用 void 指针来指向未知数据类型的位置。程序可以设置为要求用户告知数据的类型,并且可以根据用户输入的信息进行类型转换。下面给出一个代码sn-p。

      void funct(void *a, int z)
      {
          if(z==1)
          printf("%d",*(int*)a); // If user inputs 1, then he means the data is an integer and type casting is done accordingly.
          else if(z==2)
          printf("%c",*(char*)a); // Typecasting for character pointer.
          else if(z==3)
          printf("%f",*(float*)a); // Typecasting for float pointer
      }
      

      关于 void 指针,您应该牢记的另一点重要的一点是 – 指针运算不能在 void 指针中执行。

      void *ptr;
      
      int a;
      
      ptr=&a;
      
      ptr++; // This statement is invalid and will result in an error because 'ptr' is a void pointer variable.
      

      所以现在我明白了我的错误所在。我也在更正。

      参考文献:

      http://www.antoarts.com/void-pointers-in-c/

      http://www.circuitstoday.com/void-pointers-in-c.

      新代码如下所示。


      #include<stdio.h>
      #define INT 1
      #define FLOAT 2
      
      void absolute_value ( void *j, int *n)
      {
          if ( *n == INT) {
              if ( *((int*)j) < 0 )
                  *((int*)j) = *((int*)j) * (-1);
          }
          if ( *n == FLOAT ) {
              if ( *((float*)j) < 0 )
                  *((float*)j) = *((float*)j) * (-1);
          }
      }
      
      
      int main()
      {
          int i = 0,n=0;
          float f = 0;
          printf("Press 1 to enter integer or 2 got float then enter the value to get absolute value\n");
          scanf("%d",&n);
          printf("\n");
          if( n == 1) {
              scanf("%d",&i);
              printf("value entered before absolute function exec = %d \n",i);
              absolute_value(&i,&n);
              printf("value entered after absolute function exec = %d \n",i);
          }
          if( n == 2) {
              scanf("%f",&f);
              printf("value entered before absolute function exec = %f \n",f);
              absolute_value(&f,&n);
              printf("value entered after absolute function exec = %f \n",f);
          }
          else
          printf("unknown entry try again\n");
          return 0;
      }   
      

      谢谢,

      【讨论】:

        【解决方案6】:

        不,这是不可能的。解引用的值应该是什么类型?

        【讨论】:

          【解决方案7】:
          void abc(void *a, int b) {
            char *format[] = {"%d", "%c", "%f"};
            printf(format[b-1], a);
          }
          

          【讨论】:

          • 这个程序是否可行......请检查一下,如果这在 C 编程中可行......
          • 是的,这是可能的(尽管如果没有对 b 进行范围检查,代码很危险)。 Printf 接受可变数量的参数,并且 a 只是作为任何指针(没有任何类型信息)被压入堆栈,并使用格式字符串中的信息使用 va_arg 宏在 printf 函数中弹出。
          • @SiegeX:请描述什么是不可移植的,什么是未定义的。
          • @Gauthier 传递包含格式说明符的变量是不可移植的并且可能是未定义的行为。如果您想做类似的事情,请查看printf 的“vs”风格。 This answer 有一个很好的例子。
          • 在实践中,我可能会用枚举替换int b,但以后对该枚举进行任何更改都会破坏此功能。
          【解决方案8】:

          这里有一个关于void指针的简短指针:https://www.learncpp.com/cpp-tutorial/613-void-pointers/

          6.13 — 空指针

          因为void指针不知道自己指向的是什么类型的对象,所以不能直接解引用!相反,void 指针必须先显式转换为另一种指针类型,然后才能取消引用。

          如果一个 void 指针不知道它指向什么,我们怎么知道要把它转换成什么?最终,这取决于您来跟踪。

          空指针杂项

          不可能对 void 指针进行指针运算。这是因为指针运算需要指针知道它所指向的对象大小,所以它可以适当地增加或减少指针。

          假设机器的内存是字节可寻址的并且不需要对齐访问,解释void* 的最通用和原子(最接近机器级表示)的方法是作为一个字节的指针,@ 987654324@。例如,将 void* 转换为 uint8_t* 将允许您打印出从该地址开始的前 1/2/4/8/however-many-you-desire 字节,但您不能做太多否则。

          uint8_t* byte_p = (uint8_t*)p;
          for (uint8_t* i = byte_p; i < byte_p + 8; i++) {
            printf("%x ",*i);
          }
          

          【讨论】:

            【解决方案9】:

            我想让这个函数通用, 不使用 ifs;有可能吗?

            我看到的唯一简单方法是使用重载 .. 这在 C 编程语言 AFAIK 中不可用。

            您是否为您的程序考虑过 C++ 编程语言?还是有什么限制禁止使用?

            【讨论】:

            • 好吧,真可惜。从我的角度来看,我没有看到任何解决方案。实际上,它与 printf() & cout 相同:2 种不同的方式来实现打印。 printf() 使用 if() 语句来解码格式字符串(我想或类似的东西),而对于 cout 案例运算符
            【解决方案10】:

            空指针是没有与之关联的数据类型的指针。空指针可以保存任何类型的地址,并且可以被类型转换为任何类型。但是,void指针不能直接解引用。

            int x = 1;
            void *p1;
            p1 = &x;
            cout << *p1 << endl; // this will give error
            cout << (int *)(*p) << endl; // this is valid
            

            【讨论】:

              【解决方案11】:

              您可以轻松打印空打印机

              int p=15;
              void *q;
              q=&p;
              printf("%d",*((int*)q));
              

              【讨论】:

              • 谁保证voidint的大小一样?
              • 这在技术上不是打印void 指针,而是在指针包含的内存地址处打印int(在本例中为p 的值)。跨度>
              【解决方案12】:

              因为 C 是静态类型的强类型语言,所以必须在编译前确定变量的类型。当您尝试在 C 中模拟泛型时,您最终会尝试再次重写 C++,因此最好使用 C++。

              【讨论】:

                【解决方案13】:

                void 指针是通用指针。任何变量的任何数据类型的地址都可以分配给 void 指针。

                int a = 10;
                float b = 3.14;
                void *ptr;
                ptr = &a;
                printf( "data is %d " , *((int *)ptr)); 
                //(int *)ptr used for typecasting dereferencing as int
                ptr = &b;
                printf( "data is %f " , *((float *)ptr));
                //(float *)ptr used for typecasting dereferencing as float
                

                【讨论】:

                  【解决方案14】:

                  您不能在不指定类型的情况下取消引用指针,因为不同的数据类型在内存中具有不同的大小,即 int 为 4 个字节,char 为 1 个字节。

                  【讨论】:

                    【解决方案15】:

                    从根本上说,在 C 中,“类型”是一种解释内存中字节的方式。比如下面的代码是什么

                    struct Point {
                      int x;
                      int y;
                    };
                    
                    int main() {
                      struct Point p;
                      p.x = 0;
                      p.y = 0;
                    }
                    

                    说“当我运行 main 时,我想分配 4(整数大小)+ 4(整数大小)= 8(总字节)的内存。当我将 '.x' 作为左值写入一个值时类型标签指向编译时,从指针的内存位置加上四个字节检索数据。将返回值指定为编译时标签“int”。

                    在计算机运行时,您的“点”结构如下所示:

                    00000000 00000000 00000000 00000000 00000000 00000000 00000000
                    

                    您的void* 数据类型可能如下所示:(假设是 32 位计算机)

                    10001010 11111001 00010010 11000101
                    

                    【讨论】:

                    • 这不能回答问题
                    【解决方案16】:

                    这行不通,但 void * 在定义指向函数的泛型指针并将其作为参数传递给另一个函数(类似于 Java 中的回调)或将其定义为类似于 oop 的结构时有很大帮助。

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2014-03-05
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多