Search code examples
c++language-lawyerlinkage

Do GCC and clang show the same result as Visual Studio on this case, about language linkage?


[dcl.link]/4:

Linkage specifications nest. When linkage specifications nest, the innermost one determines the language linkage. A linkage specification does not establish a scope. A linkage-specification shall occur only in namespace scope. In a linkage-specification, the specified language linkage applies to the function types of all function declarators, function names with external linkage, and variable names with external linkage declared within the linkage-specification. [ Example:

extern "C"                      // the name f1 and its function type have C language linkage;
  void f1(void(*pf)(int));      // pf is a pointer to a C function

...

— end example ]

Observe that the pointer &foo passed to the function c_f() below is not a pointer to a C function. This code compiles and links normally in VS2017. But it shouldn't, according to [dcl.link]/4.

File main.cpp:

#include <stdio.h>
extern "C"                          // the name c_f and its function type have C language linkage;
void c_f(void(*pf)(int));           // pf is a pointer to a C function

void foo(int i) {
    printf("%d\n", i);
}

extern "C" void c_foo(int);

int main() {
    c_foo(1);       // Calls c_foo(int) defined in other.c
    c_f(&foo);      // Calls c_f(void(*)(int)) defined in other.c, but &foo is not a pointer to a C function !! 
}

File other.c:

#include <stdio.h>

void c_f(void(*pf)(int)){
    pf(2);
}

void c_foo(int i) {
    printf("%d\n", i);
}

I'm curious to know whether clang and GCC are compliant with the Standard, but I can't verify this in a web compiler.

Edit

It dawned on me that I really don't need two files to verify whether clang and GCC are compliant to the Standard, on the issue mentioned above. If the Standard requires the address of a C function, as an argument for the function c_f() and the code in main.cpp supplies the address of a C++ function, the C++ compiler has to complain1 when compiling this file. But that doesn't happen neither in clang nor in GCC. Then, I might as well say that both clang and GCC are also buggy on this regard.

1) If we assume that a diagnostic is required


Solution

  • Your code shows undefined behavior according to [dcl.link]/1 and [expr.call]/1 (emphases are mine):

    [dcl.link]/1:

    All function types, function names with external linkage, and variable names with external linkage have a language linkage. [ Note: Some of the properties associated with an entity with language linkage are specific to each implementation and are not described here. For example, a particular language linkage may be associated with a particular form of representing names of objects and functions with external linkage, or with a particular calling convention, etc. — end note ] The default language linkage of all function types, function names, and variable names is C++ language linkage. Two function types with different language linkages are distinct types even if they are otherwise identical.

    [expr.call]/1:

    A function call is a postfix expression followed by parentheses containing a possibly empty, comma-separated list of initializer-clauses which constitute the arguments to the function. The postfix expression shall have function type or function pointer type. For a call to a non-member function or to a static member function, the postfix expression shall be either an lvalue that refers to a function (in which case the function-to-pointer standard conversion is suppressed on the postfix expression), or it shall have function pointer type. Calling a function through an expression whose function type is different from the function type of the called function's definition results in undefined behavior ([dcl.link]). For a call to a non-static member function, the postfix expression shall be an implicit ([class.mfct.non-static], [class.static]) or explicit class member access whose id-expression is a function member name, or a pointer-to-member expression selecting a function member; the call is as a member of the class object referred to by the object expression. In the case of an implicit class member access, the implied object is the one pointed to by this. [ Note: A member function call of the form f() is interpreted as (*this).f() (see [class.mfct.non-static]). — end note ] If a function or member function name is used, the name can be overloaded, in which case the appropriate function shall be selected according to the rules in [over.match]. If the selected function is non-virtual, or if the id-expression in the class member access expression is a qualified-id, that function is called. Otherwise, its final overrider in the dynamic type of the object expression is called; such a call is referred to as a virtual function call. [ Note: The dynamic type is the type of the object referred to by the current value of the object expression. [class.cdtor] describes the behavior of virtual function calls when the object expression refers to an object under construction or destruction. — end note ]