【问题标题】:Opaque struct with C linkage & C++ implementation具有 C 链接和 C++ 实现的不透明结构
【发布时间】:2017-12-07 04:16:58
【问题描述】:

假设我想要一个带有 C 链接的结构 foo。我将在 C 风格的头文件 (foo.h) 中不透明地声明它:

struct foo;
typedef struct foo foo;

但是我想在foo的实现中使用C++。假设我希望 foo 包含 std::vector<int>。由于 C 代码无法访问 foo 的字段,我不明白为什么编译器(或语言标准)应该禁止这样做。但我该怎么做呢?我可以像这样在 foo.cc 中将foo 的实现放在extern "C" 周围吗?

extern "C" {
    struct foo {
        ....
        std::vector<int> bar;
    }
}

【问题讨论】:

  • 结构没有链接
  • ...extern "C" 也不会让您的代码编译为 C。
  • 以建设性的方式陈述同一件事,extern "C" 所做 是使所有在其范围内声明或定义的函数 (a) 使用 C 命名约定,并且(b) 使用 C 调用约定。但在所有其他方面,它仍然是 C++ 代码。

标签: c++ c linker


【解决方案1】:

我最终在 C 下大量填充了 C++...这是它的一般要点。 (顺便说一句,让 C++ 异常展开 C 堆栈可能会导致 C 代码出现问题,而这些问题不知道会发生此类事情......因此建议在 C++ 接口函数中执行一些 catch(...) 块。)

lib.h:一个头文件,它声明了一些具有 C 调用约定的函数,无论它是编译为 C 还是 C++

#pragma once

#if defined(__cplusplus)
extern "C" {
#endif

/* Looks like a typical C library interface */

struct c_class;

struct c_class *do_init();
void do_add(struct c_class *tgt, int a);
int  do_get_size(const struct c_class *tgt);
void do_cleanup(struct c_class *tgt);


#if defined(__cplusplus)
}
#endif

lib.cpp:一个 C++ 库,其中包含一些使用 C 调用约定声明的函数

#include "lib.h"

#include <iostream>
#include <vector>
#include <cstdlib>

class Foo
{
  std::vector<int> m_vec;
public:
  Foo() : m_vec() {}
  virtual ~Foo() {}

  void add(int a) {
    m_vec.push_back(a);
  }

  int getSize() {
    return m_vec.size();
  }
};


/* Exposed C interface with C++ insides */
extern "C" {

  struct c_class
  {
    Foo *guts;
  };

  struct c_class *do_init()
  {
    struct c_class *obj = static_cast<c_class*>(malloc(sizeof(struct c_class)));
    obj->guts = new Foo();
    return obj;
  }

  void do_add(struct c_class *tgt, int a) {
    tgt->guts->add(a);
  }

  int do_get_size(const struct c_class *tgt) {
    return tgt->guts->getSize();
  }

  void do_cleanup(struct c_class *tgt) {
    delete tgt->guts;
    free(tgt);
  }
}

ma​​in.c:使用从 lib 导出的 C 调用约定函数的 C 程序

#include <stdio.h>
#include "lib.h"

int main(int argc, char *argv[])
{
  int i;
  struct c_class *obj;

  obj = do_init();

  for(i = 0; i< 100; i++)
  {
    do_add(obj, i);
  }

  printf("Size: %d\n", do_get_size(obj));

  do_cleanup(obj);
}

Makefile:将C编译为C,将C++编译为C++,然后使用C++编译器进行链接的makefile

CXXFLAGS ?= -Wall -Werror -pedantic
CFLAGS ?= -Wall -Werror -pedantic

.PHONY: all
all : test

test: lib.o main.o
    $(CXX) $(CXXFLAGS) -o test lib.o main.o

lib.o: lib.cpp lib.h
    $(CXX) $(CXXFLAGS) -c $< -o $@

main.o: main.c lib.h
    $(CC) $(CFLAGS) -c $< -o $@

clean:
    -rm lib.o main.o test

输出

$ make
g++ -Wall -Werror -pedantic -c lib.cpp -o lib.o
cc -Wall -Werror -pedantic -c main.c -o main.o
g++ -Wall -Werror -pedantic -o test lib.o main.o

$ ./test
Size: 100

【讨论】:

  • 如果从析构函数中删除 virtual 关键字是否有效?如果你想与 C 兼容,你的类不能有 vtable。
  • @MaxRahm...我不明白你的问题。它适用于 Foo 中的 vtable,因为该结构只有一个指向 Foo 的指针。 (如果结构对 C 不透明,它将通过 void * 保存 Foo 对象)。 OP 要求一个可以保存 STL 的不透明结构,而不是一种制造与 C 兼容的 C++ 对象的方法。这实际上是一个 MCVE。
【解决方案2】:

只要结构的内容是 POD(plain old data),结构在 C 和 C++ 之间是兼容的。如果您将非 POD 添加到其中,例如您的向量,它不再与 C 兼容,并且在与典型的 C 函数(如 memsetmemcpy)一起使用时会导致未定义的行为。非 POD 成员导致结构的另一个内存布局,它在它的开头获取一个虚拟表以支持继承。

【讨论】:

  • 但是如果 C 看到的都是一个不透明的结构,那么成员的兼容性肯定是无关紧要的吗?
  • 如果成员有构造函数/虚拟表等,则不会。
【解决方案3】:

结构本身只是一种类型,换句话说,它是一种语法糖,它在编译之外不作为结构存在,而只是内存中的几个字节和指向它的指针。因此,它没有任何特定于语言的链接。字节的含义由编译器在编译时定义,并且在那里创建对它们的引用。因此,它的布局取决于您使用的语言。

回答您的问题,std::vector 实现包含某些字段,这些字段成为结构布局的一部分。 c++ 理解它们,因为它理解模板和所有其他面向对象的东西,'c' 将首先扼杀模板以及其他 c++sness。因此,如果您使用非 POD 数据,则结构定义不兼容。

如果你使用 POD、标准的 'c' 数据类型、没有成员函数和位域,你应该在定义上都设置和兼容。这意味着两个编译器都将编译结构并且布局将相似,因此您可以跨语言保存/恢复它。

如果您只是在谈论通过“c”代码将指针传递给 c++ 结构,那么无论如何您都应该设置好。您可以使用 cast to 'void*' 以标准方式执行此操作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-03-11
    • 1970-01-01
    • 2016-11-26
    • 2023-04-08
    • 2016-12-26
    • 2023-03-31
    • 2020-05-17
    • 1970-01-01
    相关资源
    最近更新 更多