【问题标题】:Another void* topic; I just have to ask because I am confused另一个 void* 主题;我只需要问,因为我很困惑
【发布时间】:2018-03-09 07:00:44
【问题描述】:

好的,尽管 Stack 对 void* 的细节感到困惑,像 The C Programming Language (K&R)The C++ Programming Language (Stroustrup) 这样的书。我学到了什么? void* 是一个没有类型推断的通用指针。它需要转换为任何已定义的类型,并且打印 void* 只会产生地址。

我还知道什么? void* 不能被取消引用,并且到目前为止仍然是 C/C++ 中的一项,我从中发现了很多关于但很少理解的内容。

我知道它必须被强制转换为*(char*)void*,但是对于generic 指针对我来说没有意义的是,我必须以某种方式已经知道我需要什么类型才能获取一个值。我是一名 Java 程序员;我了解泛型类型,但这是我遇到的问题。

所以我写了一些代码

typedef struct node
{
  void* data;
  node* link;
}Node;

typedef struct list
{
   Node* head;
}List;

Node* add_new(void* data, Node* link);

void show(Node* head);

Node* add_new(void* data, Node* link)
{
  Node* newNode = new Node();
  newNode->data = data;
  newNode->link = link;

  return newNode;
}

void show(Node* head)
{
  while (head != nullptr)
  {
      std::cout << head->data;
      head = head->link;
  }
}

int main()
{
  List list;

  list.head = nullptr;

  list.head = add_new("My Name", list.head);

  list.head = add_new("Your Name", list.head);

  list.head = add_new("Our Name", list.head);

  show(list.head);

  fgetc(stdin);

  return 0;
}

我稍后会处理内存释放。假设我不了解void* 中存储的类型,我该如何获取值? This 表示我已经需要知道类型,this 没有透露任何关于 void* 的通用性质,而我遵循 here 的含义,尽管仍然不了解。

为什么我期望void* 配合,编译器会自动抛出隐藏在堆或堆栈的某个寄存器中的内部类型?

【问题讨论】:

  • 我认为void*的使用是:Window的CWnd类可以容纳一个void* data。它不知道这些数据是什么,也不在乎。它对这个data 没有任何作用,它只是为我保留它。同时,我的代码可以将我的thingamabob 存储到这个data 中。我的代码假定data 始终包含thingamabob,这很好,因为我的代码是访问此data 成员的唯一代码。我的代码“知道”类型,但 CWnd 不知道。

标签: c++11 c99 void-pointers


【解决方案1】:

我稍后会处理内存释放。假设我不了解 void* 中存储的类型,我该如何获取值?

你不能。您必须知道指针可以转换为的有效类型,然后才能取消引用它。

以下是使用泛型类型的几个选项:

  1. 如果您能够使用 C++17 编译器,则可以使用 std::any
  2. 如果你能够使用 boost 库,你可以使用boost::any

【讨论】:

  • 有没有办法在c或c++中练习pure泛型?
  • @Mushy,不,不是核心语言。有尝试以std::anyboost::any 的形式制作通用对象类型
  • @RSahu 在 C++17 中有 std::any
【解决方案2】:

与 Java 不同,您在 C/C++ 中使用内存指针。没有任何封装。 void * 类型表示变量是内存中的地址。任何东西都可以存储在那里。使用int * 之类的类型,您可以告诉编译器您指的是什么。此外,编译器知道类型的大小(比如int 的 4 个字节),并且在这种情况下地址将是 4 的倍数(粒度/内存对齐)。最重要的是,如果您为编译器指定类型,它将在编译时执行一致性检查。不是之后。 void * 不会发生这种情况。

简而言之,您正在使用裸机。这些类型是编译器指令,不保存运行时信息。它也不会跟踪您动态创建的对象。它只是内存中分配的一个段,您最终可以在其中存储任何东西

【讨论】:

    【解决方案3】:

    使用 void* 的主要原因是可以指向不同的东西。因此,我可以传入 int* 或 Node* 或其他任何内容。但除非您知道类型或长度,否则您无法对它做任何事情。

    但是如果你知道长度,你可以在不知道类型的情况下处理指向的内存。将其转换为 char* 是因为它是一个单字节,所以如果我有一个 void* 和多个字节,我可以将内存复制到其他地方,或者将其归零。

    另外,如果它是一个指向类的指针,但你不知道它是父类还是继承类,你可以假设一个并在数据中找出一个标志,告诉你是哪一个。但无论如何,当您想要做的不仅仅是将它传递给另一个函数时,您需要将其转换为某种东西。 char* 只是最容易使用的单字节值。

    【讨论】:

      【解决方案4】:

      您的困惑源于处理 Java 程序的习惯。 Java代码是虚拟机的指令集,其中RAM的功能被赋予一种数据库,该数据库存储每个对象的名称、类型、大小和数据。您现在正在学习的编程语言旨在编译为 CPU 指令,具有与底层操作系统相同的内存组织。 C 和 C++ 语言使用的现有模型是建立在大多数流行操作系统之上的一些抽象,以使代码在为该平台和操作系统编译后可以有效工作。自然,该组织不涉及关于类型的字符串数据,除了著名的 C++ 中的 RTTI。

      对于您的情况,不能直接使用 RTTI,除非您在裸指针周围创建一个包装器,用于存储数据。

      事实上,C++ 库包含大量可用且可移植的容器类模板(如果它们是由 ISO 标准定义的)。标准的 3/4 只是对通常称为 STL 的库的描述。使用它们比使用裸指针更可取,除非您出于某种原因想要创建自己的容器。对于特定任务,只有 C++17 标准提供 std::any 类,以前存在于 boost 库中。当然,可以重新实现它,或者在某些情况下,用std::variant 替换。

      【讨论】:

        【解决方案5】:

        假设我不了解 void* 中存储的类型,我该如何获取值

        你没有。

        您可以做的是记录存储在void*中的类型。

        中,void* 用于通过一个抽象层传递指向某物的二进制数据块,并在另一端接收它,并将其转换回代码知道的类型会通过的。

        void do_callback( void(*pfun)(void*), void* pdata ) {
          pfun(pdata);
        }
        
        void print_int( void* pint ) {
          printf( "%d", *(int*)pint );
        }
        
        int main() {
          int x = 7;
          do_callback( print_int, &x );
        }
        

        在这里,我们忘记了&amp;x 的类型,将其传递给do_callback

        它稍后被传递到代码内部 do_callback 或其他知道 void* 实际上是int* 的地方。所以它将它转换回来并将其用作int

        void* 和消费者 void(*)(void*) 是耦合的。上面的代码是“可证明正确的”,但证明并不在于类型系统;相反,这取决于我们仅在知道它是 int* 的上下文中使用 void* 的事实。


        在 C++ 中,您可以类似地使用void*。但你也可以变得花哨。

        假设您想要一个指向任何可打印内容的指针。如果可以将 &lt;&lt; 转换为 std::ostream,则可以打印。

        struct printable {
          void const* ptr = 0;
          void(*print_f)(std::ostream&, void const*) = 0;
        
          printable() {}
          printable(printable&&)=default;
          printable(printable const&)=default;
          printable& operator=(printable&&)=default;
          printable& operator=(printable const&)=default;
        
          template<class T,std::size_t N>
          printable( T(&t)[N] ):
            ptr( t ),
            print_f( []( std::ostream& os, void const* pt) {
              T* ptr = (T*)pt;
              for (std::size_t i = 0; i < N; ++i)
                os << ptr[i];
            })
          {}
          template<std::size_t N>
          printable( char(&t)[N] ):
            ptr( t ),
            print_f( []( std::ostream& os, void const* pt) {
              os << (char const*)pt;
            })
          {}
          template<class T,
            std::enable_if_t<!std::is_same<std::decay_t<T>, printable>{}, int> =0
          >
          printable( T&& t ):
            ptr( std::addressof(t) ),
            print_f( []( std::ostream& os, void const* pt) {
              os << *(std::remove_reference_t<T>*)pt;
            })
          {}
          friend
          std::ostream& operator<<( std::ostream& os, printable self ) {
            self.print_f( os, self.ptr );
            return os;
          }
          explicit operator bool()const{ return print_f; }
        };
        

        我刚刚做的是一种在 C++ 中称为“类型擦除”的技术(有点类似于 Java 类型擦除)。

        void send_to_log( printable p ) {
          std::cerr << p;
        }
        

        Live example.

        在这里,我们为在类型上打印的概念创建了一个特别的“虚拟”接口。

        类型不需要支持任何实际的接口(没有二进制布局要求),它只需要支持某种语法。

        我们为任意类型创建自己的虚拟调度表系统。

        这在 C++ 标准库中使用。在 中有std::function&lt;Signature&gt;,在 中有std::any

        std::anyvoid*,它知道如何销毁和复制其内容,如果您知道类型,则可以将其转换回原始类型。您也可以查询它并询问它是否为特定类型。

        std::any 与上述类型擦除技术混合使用可以让您创建具有任意鸭子类型接口的常规类型(其行为类似于值,而不是引用)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-04-24
          • 2011-04-11
          • 2020-08-17
          • 2012-08-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多