5

[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;
 }
Chef Gladiator
  • 902
  • 11
  • 23

2 Answers2

4

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.

Acorn
  • 24,970
  • 5
  • 40
  • 69
  • @Acorn thanks but, GNU and CLANG have no issues anywhere in this area. It is only MSVC. Are we saying here both of them are wrong? I have tried doing it as C with MSVC but the warning stayed. Surely returning **any** kind of a struct is not a novelty in C any more? One might think ... – Chef Gladiator Aug 09 '19 at 20:58
  • 1
    @ChefGladiator it is rather like that the C/C++ interoperability is somewhat underspecified and might be implementation-defined? And it becomes quality-of-implementation issue and it might be that MSVC is just of inferior quality. – Antti Haapala -- Слава Україні Aug 09 '19 at 21:27
  • @ChefGladiator but acorn just proved that your code wouldn't work. – Antti Haapala -- Слава Україні Aug 10 '19 at 05:40
  • In the C++ standard, an implicitly deleted function is not user-defined. (The standard defines the term "user-defined"). Not sure what the ABi spec means by it though... – M.M Aug 10 '19 at 10:12
  • @ChefGladiator You are mixing the language (which compilers *usually* try to implement as closely as possible) with the ABI (which has nothing to do with the language). Further, you seem to believe that the linkage specification has something to do with being "C code", which is not the case. The code is still C++ and is compiled as C++. – Acorn Aug 10 '19 at 13:03
  • @ChefGladiator Of course, why would you expect a warning if it not `extern "C"`? – Acorn Aug 10 '19 at 13:29
  • @ChefGladiator Any compiler following that calling convention will have the same issue. In other words, this is not about a particular language or a particular compiler. – Acorn Aug 10 '19 at 17:21
  • @Acorn "Further, you seem to believe that the linkage specification has something to do with being "C code", which is not the case. " ... hm.. where is that coming from? In a C++ program extern "C" is not a standard C. cl.exe implements it one way and clang and GCC are both implementing it in the same way but different from cl.exe. That is the simple bottom line. – Chef Gladiator Oct 29 '22 at 20:49
  • @AnttiHaapala--СлаваУкраїні -- that is indeed underspecified, ditto the : [N2644](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2644.pdf) effort is underway. – Chef Gladiator Oct 29 '22 at 21:02
2

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.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • +1 for a sober answer. extern "C" reveals a bit more unfortunate state of affairs, and here is the current official attempt to specify the C / C++ commonalities: N2644, [Programming languages — a common C/C++ core specification](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2644.pdf) – Chef Gladiator Oct 29 '22 at 20:55