另一个无效*主题;我只是要问,因为我很困惑

时间:2017-09-27 17:23:26

标签: c++11 c99 void-pointers

好吧,虽然在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在我遵循here之后却没有透露void*的一般性质,尽管仍然没有理解。

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

5 个答案:

答案 0 :(得分:5)

  

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

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

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

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

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

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

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

此外,如果它是指向类的指针,但您不知道它是父类还是继承类,您可以假定一个并在数据中找到一个标志,告诉您哪一个。但无论如何,当你想做更多的事情而不是把它传递给另一个函数时,你需要把它当作某种东西。 char *只是最容易使用的单字节值。

答案 3 :(得分:0)

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

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

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

答案 4 :(得分:0)

  

假设我不了解存储在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 );
}

在这里,我们忘记&x的ype,将其传递给do_callback

稍后将其传递给 do_callback内的代码或其他知道的代码void*实际上是int*。因此它会将其转换回来并将其用作int

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

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

假设您想要一个指向任何可打印的指针。如果可以<<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<Signature>,在中有std::any

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

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