3

[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:

#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 ;
}

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 rule 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 G++ could not care less: https://wandbox.org/permlink/7XfH2i21Yfnb7BDw of course.

Is this just a bug in VS2019, or I made a bug?

Update

Even if I move the implementation of make_Test into 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
  • 772
  • 5
  • 22

1 Answers1

1

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
  • 23,483
  • 4
  • 35
  • 66
  • @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
  • @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