C elements that are not supported in C++🚫

c-elements-that-are-not-supported-in-c++

C is a classic language for developing system software and any software for microprocessors. Linux, most of Windows and MacOS are written on it. If you take any modern wearable gadget or electronic device, in most cases they also run under the control of a C program. There is a huge amount of code in the world that is written in C, and more will be written.

C++ is the choice of those who need all the power of C and the flexibility of object-oriented programming at the same time. Counter-Strike, StarCraft, and World of Warcraft are written in C++, which means you can combine the performance of C with modern technology. Part of the Unity engine is also written in C++ to get direct access to system memory and resources.

To briefly describe the difference between these languages, C++ is an improved C. These languages have 99% the same syntax and commands, but C is more about structural and procedural programming, while C++ is about object-oriented.

In this article, I will share a list of C code examples that are not C++ correct or exhibit some C-specific behavior. Note that it is in one direction: C code, which is incorrect from the point of view of C ++.

Of course, the C language has many significant differences from the C++ language, and it will not be difficult for anyone to give examples of incompatibilities based, for example, on keywords or other obvious C99 exclusive features. You won’t find it on this list. My main criterion for choosing examples was that the code should look at first sight “innocent” enough for a C++ observer (i.e., not contain conspicuous C-exclusives), but nevertheless be specific for the C language.

*With [C23], I will mark items that will become irrelevant with the release of C23.

🔢List of 26 Elements🔢

1. In C, it is allowed to “lose” the trailing when initializing a character array with a string literal:

char s[4] = "1234";

In C++, such initialization is incorrect.

2. C supports tentative definitions. In one translation unit, you can make multiple external definitions of the same object without an initializer:

int a;
int a;
int a, a, a;

Such multiple definitions are not allowed in C++.

3. The C language allows the definition of external objects of incomplete types, provided that the type is redefined and becomes complete somewhere further in the same translation unit:

struct S s; 
struct S { int i; };

At the level of rationale, this possibility is most likely only a consequence of the previous paragraph, i.e. support for tentative definitions.
The above sequence of declarations is incorrect from the point of view of C++: the C++ language immediately forbids defining objects of incomplete types.

4. In C, you can make a non-defining entity declaration of an incomplete type void.

extern void v;

However, the corresponding definition cannot be made in C because void is an incomplete type.
In C++, you can’t even make a non-defining declaration.

5. The C language allows variables to be defined with the const qualifier without explicit initialization:

void foo(void)
{
  const int a;
}

In C++, such a definition is incorrect.

6. The C language allows declarations of new types inside the cast operator, inside the sizeof operator, and in function declarations (return types and parameter types):

int a = sizeof(enum E { A, B, C }) + (enum X { D, E, F }) 0;
/* The following code uses the declarations made above */
enum E e = B; 
int b = e + F;

Such declarations are not allowed in C++.

7. In C, an “unfamiliar” struct type name mentioned in a function’s parameter list is a declaration of a new type local to that function. At the same time, in the list of function parameters, this type can be declared as incomplete, and “additionally declared” to the complete type already in the function body:

/* Let the type of `struct S` not yet be declared at this point */

void foo(struct S *p)    /* First occurrence of `struct S` */
{
   struct S { int a; }s; /* It's still the same `struct S` */
   p = &s;
   p->a = 5;
}

In this code, everything is correct from the point of view of the C language: p has the same type as &s and contains the field a.

From the point of view of the C++ language, the mention of an “unfamiliar” class-type name in the list of function parameters is also a declaration of a new type. However, this new type is not local: it is considered to belong to the enclosing namespace. Therefore, from the point of view of the C++ language, the local definition of the type S in the body of the function has nothing to do with the type S mentioned in the parameter list. The assignment p = &s is not possible due to a type mismatch. The above code is incorrect from a C++ point of view.

8. The C language allows transfer of control to the scope of an automatic variable that “jumps” over its initialization declaration:

switch (1)
{
  int a = 42;
case 1:;
}

Such a transfer of control is not allowed from the point of view of C++.

9. Since C99, implicit blocks have appeared in the C language: some statements are themselves blocks and, in addition, induce nested subblocks. For example, the for loop itself is a block, and the loop body is a separate block nested in the for loop block. For this reason, the following code is legal in C:

for (int i = 0; i < 10; ++i)
{ 
  int i = 42; 
}

The variable i declared in the body of the loop has nothing to do with the variable i declared in the head of the loop.

In the C++ language, in such a situation, both the loop header and the loop body form a single scope, which excludes the possibility of a "nested" declaration of i.

10. The C language allows the use of meaningless storage-class specifiers in declarations that do not declare any objects:

static struct S { int i; };

This is not allowed in C++.

Additionally, you can notice that in the C language, typedef is also formally just one of the storage-class specifiers, which allows you to create meaningless typedef declarations that do not declare aliases:

typedef struct S { int i; };

C++ does not allow such typedef declarations.

To be fair, such declarations in C are not completely meaningless: they still declare a struct S type.

11. The C language allows explicit repetition of cv-qualifiers in declarations:

const const const int a = 42;

The code is incorrect from a C++ point of view. (C++ also turns a blind eye to similar over-qualification, but only through intermediate type names: typedef names, typical template parameters).

12. In C, direct copying of volatile objects is not a problem (at least from the point of view of formal code correctness):

void foo(void)
{
  struct S { int i; }; 
  volatile struct S v = { 0 }; 
  struct S s = v;
  s = v;
}

In C++, implicitly generated copy constructors and assignment operators do not take volatile objects as arguments.

13. In C, any integral constant expression with a value 0 can be used as a null pointer constant:

void *p = 2 - 2;
void *q = -0;

This was also the case in C++ before the adoption of the C++11 standard. However, in modern C++, of integral values, only the literal null value can act as a null pointer constant, but more complex expressions are no longer valid. The above initializations are incorrect from a C++ point of view.

14. The C does not support cv-qualification for rvalues. In particular, the cv-qualification of a function's return value is immediately ignored by the language. Together with the automatic conversion of arrays to pointers, this allows you to bypass some rules of constant correctness:

struct S { int a[10]; };

const struct S foo()
{
  struct S s;
  return s;
}

int main()
{
  int *p = foo().a;
}

It's worth noting, however, that attempting to modify an rvalue in C results in undefined behavior.

From a C++ perspective, the return value of foo() and hence the array foo().a retains a const-qualification, and implicit conversion of foo().a to type int * is not possible.

15. [C23] The C preprocessor is not familiar with literals such as true and false. In C, true and false are available only as macros defined in the standard header . If these macros are not defined, then according to the rules of the preprocessor, both #if true and #if false should behave like #if 0.

At the same time, the C++ preprocessor must naturally recognize true and false literals, and its #if directive must behave in the "expected" way with these literals.

This can be a source of incompatibilities when the C code does not include :

#if true
int a[-1];
#endif

This code is obviously incorrect in C++, but at the same time, it can be easily compiled in C.

16. Starting with C++11, the C++ preprocessor no longer treats the sequence as independent tokens. From the point of view of the C++ language, in this situation is a literal suffix. To avoid this interpretation, in C++ these tokens should be separated by a space:

#define D "d"

int a = 42;
printf("%"D, a);

This format for printf is correct for C, but incorrect from a C++ point of view.

17. Recursive calls of main function are allowed in C, but not in C++. C++ programs are generally not allowed to use the main function in any way.

18. In C, string literals are of type char [N], while in C++ they are const char [N]. Even if "old" C++ supports the conversion of a string literal to type char * as an exception, this exception only works when applied directly to the string literal

char *p = &"abcd"[0];

Such initialization is incorrect from the point of view of C++.

19. In C, a bit field declared as type int without an explicit indication of signed or unsigned can be either signed or unsigned (this is implementation-defined). In C++, such a bit field is always signed.

20. In C, typedef type names and struct type tags are in different namespaces and do not conflict with each other. For example, such a set of declarations is correct from the point of view of the C:

struct A { int a; };
typedef struct B { int b; } A;
typedef struct C { int c; } C;

In C++, there is no separate concept of a tag for class-types: class names share the same namespace with typedef names and may conflict with them. For partial compatibility with C code, C++ allows you to declare typedef aliases that match the names of existing type classes, but only if the alias refers to a type class with exactly the same name. In the above example, the typedef declaration on line 2 is incorrect from a C++ point of view, but the declaration on line 3 is correct.

21. In C, you can use a field name that matches an existing type name.

typedef int I;

struct S
{
  I I;
};

In C++, such "redefinition" of an identifier is not allowed.

22. In C, an implicit conflict between external and internal linking when declaring the same variable results in undefined behavior, but in C++, such a conflict makes the program ill-formed. To arrange such a conflict, you need to build a rather tricky configuration:

static int a; /* Internal linking */

void foo(void) 
{ 
  int a; /* Hides external `a`, has no linking */

  {
    extern int a; 
    /* Because external `a` is hidden, declare `a` with internal
        linking. Now `a` is declared with both external 
and internal linking - conflict */ 
  } 
}

In C++, such an extern declaration is ill-formed. Although there is a separate example in the C++ language standard for this unusual situation, popular C++ compilers generally do not diagnose this violation.

The following are examples of differences that I think are trivial, well-known, and uninteresting.

I include them here for completeness and because they formally satisfy my criterion: at first glance, the code looks more or less normal to the eyes of a C++ observer.

23. The C language allows implicit conversion of pointers from a void * type:

void *p = 0;
int *pp = p;

24. In C, values of enum type are implicitly convertible to and from int type:

enum E { A, B, C } e = A;
e = e + 1;

In C++, implicit conversion only works one way.

25. [C23] The C language supports function declarations without prototypes:

void foo(); /* Declaration without prototype */

void bar() 
{ 
  foo(1, 2, 3); 
}

26. In C, nested struct type declarations place the name of the internal type in the external (enclosing) scope:

struct A 
{ 
  struct B { int b; } a;
};

struct B b; /* Refers to the type `struct B` declared on line 3 */

Conclusion

That, in fact, is all that has accumulated at the moment. I hope you find my observations interesting and they will help someone.
Do you think I missed something important? Feel free to leave any questions, comments, or suggestions.

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
project-initiation:-how-to-start-your-project-off-right

Project Initiation: How to Start Your Project Off Right

Next Post
product-marketing-summit-|-new-york-|-march-15-&-16-|-2023

Product Marketing Summit | New York | March 15 & 16 | 2023

Related Posts