Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to "play" with parameter constness in extern "C" declarations?

Suppose I'm using some C library which has a function:

int foo(char* str);

and I know for a fact that foo() does not modify the memory pointed to by str. It's just poorly written and doesn't bother to declare str being constant.

Now, in my C++ code, I currently have:

extern "C" int foo(char* str);

and I use it like so:

foo(const_cast<char*>("Hello world"));

My question: Is it safe - in principle, from a language-lawyering perspective, and in practice - for me to write:

extern "C" int foo(const char* str);

and skip the const_cast'ing?

If it is not safe, please explain why.

Note: I am specifically interested in the case of C++98 code (yes, woe is me), so if you're assuming a later version of the language standard, please say so.

like image 831
einpoklum Avatar asked Nov 10 '20 09:11

einpoklum


People also ask

Do C++ features are allowed in extern C block?

2. Function names may not be changed in C as it doesn't support function overloading. To avoid linking problems, C++ supports the extern “C” block. C++ compiler makes sure that names inside the extern “C” block are not changed.

Can you use extern C in C?

By declaring a function with extern "C" , it changes the linkage requirements so that the C++ compiler does not add the extra mangling information to the symbol. This pattern relies on the presence of the __cplusplus definition when using the C++ compiler. If you are using the C compiler, extern "C" is not used.

Does C need extern?

Using extern "C" lets the compiler know that we want to use C naming and calling conventions. This causes the compiler to sort of entering C mode inside our C++ code. This is needed because C++ compilers mangle the names in their symbol table differently than C compilers and hence behave differently than C compilers.

What does extern C mean in CPP?

The extern must be applied to all declarations in all files. (Global const variables have internal linkage by default.) extern "C" specifies that the function is defined elsewhere and uses the C-language calling convention. The extern "C" modifier may also be applied to multiple function declarations in a block.


Video Answer


1 Answers

Is it safe for me to write: and skip the const_cast'ing?

No.

If it is not safe, please explain why.

-- From language side:

After reading the dcl.link I think exactly how the interoperability works between C and C++ is not exactly specified, with many "no diagnostic required" cases. The most important part is:

Two declarations for a function with C language linkage with the same function name (ignoring the namespace names that qualify it) that appear in different namespace scopes refer to the same function.

Because they refer to the same function, I believe a sane assumption would be that the declaration of a identifier with C language linkage on C++ side has to be compatible with the declaration of that symbol on C side. In C++ there is no concept of "compatible types", in C++ two declarations have to be identical (after transformations), making the restriction actually more strict.

From C++ side, we read c++draft basic#link-11:

After all adjustments of types (during which typedefs are replaced by their definitions), the types specified by all declarations referring to a given variable or function shall be identical, [...]

Because the declaration int foo(const char *str) with C language linkage in a C++ translation unit is not identical to the declaration int foo(char *str) declared in C translation unit (thus it has C language linkage), the behavior is undefined (with famous "no diagnostic required").

From C side (I think this is not even needed - the C++ side is enough to make the program have undefined behavior. anyway), the most important part would be C99 6.7.5.3p15:

For two function types to be compatible, both shall specify compatible return types. Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types [...]

Because from C99 6.7.5.1p2:

For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.

and C99 6.7.3p9:

For two qualified types to be compatible, both shall have the identically qualified version of a compatible type [...]

So because char is not compatible with const char, thus const char * is not compatible with char *, thus int foo(const char *) is not compatible with int foo(char*). Calling such a function (C99 6.5.2.2p9) would be undefined behavior (you may see also C99 J.2)

-- From practical side:

I do not believe will be able to find a compiler+architecture combination where one translation unit sees int foo(const char *) and the other translation unit defines a function int foo(char *) { /* some stuff */ } and it would "not work".

Theoretically, an insane implementation may use a different register to pass a const char* argument and a different one to pass a char* argument, which I hope would be well documented in that insane architecture ABI and compiler. If that's so, wrong registers will be used for parameters, it will "not work".

Still, using a simple wrapper costs nothing:

static inline int foo2(const char *var) {
    return foo(static_cast<char*>(var));
}
like image 160
KamilCuk Avatar answered Oct 12 '22 22:10

KamilCuk