【问题标题】:Tales from the MSVC extern "C"来自 MSVC 外部“C”的故事
【发布时间】:2019-08-09 14:14:31
【问题描述】:

[this question 有一个我可以找到的 SO 重复项,但该答案完全错误,请参阅下面的 C 代码。]

我了解extern "C" 不会在您的 C++ 中间生成 C 代码。它只是一个链接指令。

我有一些extern "C" 的故事要讲,但今天有一个让我感到困扰。这是一个完全最新的VS2019,代码如下:

#ifdef __cplusplus
extern "C" {
 #endif

// NOTE: in here it is still C++ code, 
// extern "C" is a linkage directive

typedef struct Test Test;

struct Test { 

    const  /* remove this const and MSVC makes no warning */
        uint32_t x; 
} ;

/*
MSVC throws warning C4190:  'make_Test' has C-linkage specified, 
   but returns UDT 'Test' which is incompatible with C
:  see declaration of 'Test'
*/
inline constexpr Test make_Test(uint32_t x)
{
    return Test{ x };
}

#ifdef __cplusplus
  }
#endif

int main(const int argc, const char * argv[])
{
    constexpr auto test_{ make_Test(42) };
    return 42 ;
}

关于那个 const 的评论是我问题的要点。

MSVC extern "C" 在很大程度上(完全?)没有记录。因此,我无法判断我是否在这个无证区域违反了某些规则?许多人声称这是某种“未完全实施”的 C11?

AFAIK 具有 C11(或任何其他 C)结构成员类型的 const 是完全可以的。好老的 G++ 也不在乎:https://wandbox.org/permlink/7XfH2i21Yfnb7BDw 当然。

这只是VS2019的一个bug,还是我自己弄的一个bug?

更新

即使我将 make_Test 的实现移动到单独的 C 文件中,并将其显式编译为 C,此警告仍将保持不变。

关于之前the same question 的“答案”。 C 可以有 const struct 数据成员,当然,C 结构可以在创建时进行列表初始化。请看下面的代码:

// gcc prog.c -Wall -Wextra -std=gnu11 "-Wno-unused-parameter" "-Wno-unused-variable"

#include <stdlib.h>

typedef struct Test { const long x; } Test;

static struct Test make_Test(long x)
{
    struct Test  test_ = { x } ;
    return test_;
 }
 int main(const int argc, const char * argv[])
 {
  struct Test test_ = make_Test(42) ;
   return 42;
 }

【问题讨论】:

  • 我重新打开了它。 The other question 这是重复的,只有错误的答案。
  • inline 在标头和constexpr 中的定义我认为不符合 C 标准。构造函数调用Test{ x } 两者都不是(应该是return (Test) { x = x };
  • 好的,看看上面的 C 代码段,什么是合法的 C。其余的这是一个警告,但仅由最新的 MSVC 生成。
  • 我很确定您应该研究一下适合 C 链接的 C++ 标准
  • 对于 C,ISO C一直支持const成员

标签: c++ c visual-c++


【解决方案1】:

x64 calling convention docs 解释说,如果 UDT 足够小并且符合某些标准,则会在 eax 中返回:

要在 RAX 中按值返回用户定义的类型,它的长度必须为 1、2、4、8、16、32 或 64 位。它还必须没有用户定义的构造函数、析构函数或复制赋值运算符;没有私有或受保护的非静态数据成员;没有引用类型的非静态数据成员;没有基类;没有虚函数;并且没有不符合这些要求的数据成员。

虽然 Test 是一个 StandardLayout 类型(因此我们希望它能够工作),但 const 非静态数据成员会删除复制赋值运算符,这可能是他们的意思是,即使它说“用户定义”。顺便说一句,这使它成为非 TrivialType,因此不是 C++03 意义上的 POD。

同样,x86 calling convention docs 也有类似的解释:

不是 POD 的结构不会在寄存器中返回。

例如,如下函数:

Test f(void)
{
    Test test = { 12345 };
    return test;
}

当在 x86/x64 C++ 模式下编译时,Test 被视为非 POD,因此 eax/rax 包含文档所引导的对象的地址。

但是,当在 x86/x64 C 模式下编译时,Test 被认为是 POD(我们正在编译 C),因此您将直接在 eax 中获得 uint32_t 值。

因此,即使我们将语言链接设置为 C,从 C 调用 f 也不起作用,这就是出现警告的原因。

【讨论】:

  • @Acorn 谢谢,但 GNU 和 CLANG 在这方面没有任何问题。它只是 MSVC。我们在这里说他们两个都是错的吗?我曾尝试使用 MSVC 将其作为 C 来执行,但警告仍然存在。确定返回 any 类型的结构在 C 中不再是新事物了吗?有人可能会想……
  • @ChefGladiator 有点像 C/C++ 互操作性没有明确说明,可能是实现定义的?它成为实施质量问题,可能是 MSVC 质量较差。
  • @ChefGladiator 但橡子刚刚证明你的代码行不通。
  • 在 C++ 标准中,隐式删除的函数不是用户定义的。 (该标准定义了术语“用户定义”)。虽然不确定 ABi 规范的含义...
  • @ChefGladiator 您正在将语言(编译器通常尝试尽可能接近地实现)与 ABI(与语言无关)混合在一起。此外,您似乎认为链接规范与“C 代码”有关,但事实并非如此。代码仍然是 C++,编译为 C++。
猜你喜欢
  • 1970-01-01
  • 2015-03-21
  • 1970-01-01
  • 2021-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-05
  • 1970-01-01
相关资源
最近更新 更多