另一个 void* 主题;我只是想问因为我很困惑

Another void* topic; I just have to ask because I am confused

好吧,尽管 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 implies I already need to know the type, and this reveals nothing about the generic nature of void* while I follow what is here虽然还是不懂

为什么我希望 void* 合作并且编译器自动将隐藏在堆或堆栈上的某个寄存器内部的类型抛出?

I'll handle the memory deallocation later. Assuming I have no understanding of the type stored in void*, how do I get the value out?

你不能。在取消引用之前,您必须知道指针可以转换为的有效类型。

这里有几个使用通用类型的选项:

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

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

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

使用 void* 的主要原因是可能指向不同的事物。因此,我可以传入一个 int* 或 Node* 或其他任何东西。但是,除非您知道类型或长度,否则您将无能为力。

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

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

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

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

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

代替

Assuming I have no understanding of the type stored in void*, how do I get the value out

你不知道。

你可以做的是记录存储在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 );
}

在这里,我们忘记 &x 的类型,通过 do_callback.

传递它

稍后传递给代码 inside do_callback 或其他地方 知道 void* 实际上是一个 int*。所以它把它投回去并将它用作 int.

void* 和消费者 void(*)(void*) 是耦合的。上面的代码是"provably correct",但证明不在于类型系统;相反,这取决于我们只在知道它是 int*.

的上下文中使用 void* 这一事实

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

假设您想要一个指向任何 printable 的指针。如果某物可以 <<std::ostream.

,则它是 printable
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++ 中称为 "type erasure" 的技术(有点类似于 Java 类型擦除)。

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

Live example.

这里我们创建了一个特殊的 "virtual" 界面来表达在类型上打印的概念。

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

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

这是在C++标准库中使用的。在 there is std::function<Signature>, and in 中有 std::any.

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

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