Reputation: 1008
[this question has one SO duplicate I can find, but that answer is plain wrong, see the C code below.]
I understand extern "C"
does not produce C code in the middle of your C++. It is just a linkage directive.
I have a few of these extern "C"
tales to tell, but here is one that bothers me today. This is a completely up-to-date VS2019, and this is the code:
#include <stdint.h>
#include <stdlib.h>
#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 {
/* remove this const and MSVC makes no warning
leave it in and MSVC complains, a lot
GCC or clang could not care less
*/
const
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( void )
{
constexpr auto test_{ make_Test(42) };
return test_.x ;
}
Link to the mandatory GODBOLT: https://godbolt.org/z/ecdz1vqhq
That comment about that const is the gist of my question.
MSVC extern "C" is largely (completely?) undocumented. Thus I am unable to tell if, I am breaking some rules in this undocumented zone. Many claim this is some kind of "not fully implemented" C11 in there.
AFAIK having that const for a C11 (or any other C) struct member type is quite OK. And good old GCC could not care less of course. As visible i that GODBOLT on-line.
Is this just a bug in VS2019, or is it me who made a bug?
Update
Even if I move the implementation of make_Test
into a separate C file, and explicitly compile it as C, this Warning will stay the same.
About that 'answer' from the same question from before. C can have const struct data members, and of course, C structs can be list initialized when made. See the code below:
// 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;
}
Upvotes: 5
Views: 718
Reputation: 119877
There is no requirement in the C++ standard that a C++ implementation come in a matched pair with a C implementation.
There is no requirement in the C++ standard that all constructs that have similar meaning in C and C++ should be binary compatible between a C++ implementation and all, or some, C implementations. There is not even a requirement anywhere that two C implementations on the same platform should be compatible.
To give a very simple example, one can have an implementation (C or C++) with sizeof(long) == 4
and another implementation on the same platform (C or C++) with sizeof(long) == 8
, and there is absolutely nothing wrong with that.
Getting back to the specific construct in the question, there is no requirement anywhere that any specific struct
, while being completely legal in both C and C++, has the same layout or the same parameter passing convention in a specific C implementation and a specific C++ implementation.
extern "C"
helps the programmer produce code that interoperates with C, but the standard cannot guarantee that any specific construct will work, simply because the C++ standard does not govern C implementations.
TL;DR this is not a case of non-conformance to any standard, this is an unfortunate but completely legal incompatibility between a specific C++ implementation and a specific C implementation. Since they both come from the same producer, they know about the incompatibility, and you get a nice warning.
Upvotes: 2
Reputation: 26066
The x64 calling convention docs explain that UDTs are returned in eax
if they are small enough and fit some criteria:
To return a user-defined type by value in RAX, it must have a length of 1, 2, 4, 8, 16, 32, or 64 bits. It must also have no user-defined constructor, destructor, or copy assignment operator; no private or protected non-static data members; no non-static data members of reference type; no base classes; no virtual functions; and no data members that do not also meet these requirements.
While Test
is a StandardLayout type (and as such we would expect it to work), the const
non-static data member makes the copy assignment operator deleted, which is probably what they mean, even if it says "user-defined". This makes it, by the way, a non TrivialType and therefore not a POD in the C++03 sense.
Similarly, the x86 calling convention docs explain something similar:
Structures that are not PODs will not be returned in registers.
For instance, a function like the following:
Test f(void)
{
Test test = { 12345 };
return test;
}
When compiled under x86/x64 C++ mode, Test
is considered a non-POD and therefore eax
/rax
contains the address of the object as the docs lead us to expect.
However, when compiler under x86/x64 C mode, Test
is considered a POD (we are compiling C) and therefore you will get the uint32_t
value directly in eax
.
Therefore, calling f
from C won't work, even if we set the language linkage to C, which is why the warning appears.
Upvotes: 4