What elements of C are unsupported in C ++? What code in C will not be accepted by the C ++ compiler? Particularly interested in the behavior of g ++.

  • The criterion "will be accepted by the compiler" is not completely clear. Compilers C, in accordance with the requirements of the language standard, are required to issue a diagnostic message when an "error" is detected in the code, after which they have the right to continue compiling. The code is not considered a C code, and its behavior is not defined from the point of view of the C language. With C ++ compilers, the situation is exactly the same. Here, the C compiler found an error, issued a diagnostic message, as required by the standard, and went on to compile. This is how to count: "accepted" or "not accepted"? - AnT
  • @AnT maybe I didn’t understand something, since you are so surprised, but in my perceiving it is “adopted”, it means that it will compile without errors, will generate an object code, in a volume corresponding to the source code. - αλεχολυτ
  • @alexolut: What is a "mistake" in your understanding? Compilations like GCC are known to report language "errors" as "warnings." Are such "errors" reported as "warnings" - are these "errors" or not? This behavior also depends on the compiler settings. What kind of state are you talking about? - AnT
  • In the TS, the question is about g ++, it is logical to assume that all possible extensions and relaxations would be quite satisfactory. Unfortunately, it is no longer active on the site, and we are unlikely to find out in a short time or at all. - αλεχολυτ

7 answers 7

The development of C ++ and C divided. C ++ has its own standard, C has its own. C ++ supports all features of C89, but does not support newer ones - C99 and C11. Of course, this depends on the compiler implementation, but the C ++ 11 standard still only includes the features of C89. True, it added a long long, which appeared in C99, and a couple more possibilities.

I myself do not know C features, because I use C ++, but, judging by Wikipedia, C99 has the following features that are missing in C ++:

  1. Variable length arrays
  2. Typical Mathematical Functions (tgmath.h)
  3. Designed Initializers
  4. Compound constants
  5. Restrict restrictions for more aggressive code optimization

In C11:

  1. Type-generic expressions using the _Generic keyword
  2. Complex numbers
  • one
    Thanks for the answer. Can I be more specific? - skegg
  • 2
    Thank. It will be necessary to get acquainted with all this. Although, it seems to me, in C ++ there are also arrays of variable length. - skegg
  • one
    > Although, it seems to me, in C ++ there are also arrays of variable length. I heard that in g ++ there is, but this, in my opinion, is not according to the standard. - gammaker

The C language is significantly different from the C ++ language from the very beginning of its existence. (It is clear that the new properties of the C99 language allow us to easily write examples of C code that will not be compiled in C ++, but in fact this does not necessarily apply to C99. Even the “classic” standard C - C89 / 90 is noticeably different from C ++.)

There are many serious differences, but according to the question, we are only interested in the differences that make correct C code incorrect in C ++. Without pretending to be complete, I will try to list these differences and provide code examples based on these differences. The key here is that the syntactic constructs used are present in both languages, i.e. at first glance, the code looks quite innocent from the point of view of the C ++ language.

  1. In C, it is allowed to "lose" the closing '\0' when initializing an array of characters with a string literal

     char s[4] = "1234"; 

    The code is incorrect from the point of view of C ++.

  2. C supports tentative definitions. Multiple external definitions of the same object can be made in one translation unit.

     /* На уровне файла */ int a; int a; int a, a, a; 

    The code is incorrect from the point of view of C ++.

  3. In the C language, typedef-type names and tags of struct-types are located in different namespaces and do not conflict with each other. For example, such a set of ads is valid in C

     struct A { int i; }; typedef struct B { int i; } A; typedef struct C { int i; } C; 

    In C ++, there is no separate “tag” concept for class types: class names share the same namespace with typedef names and may conflict with them. For partial backward compatibility with cross-compiled idiomatic C code, the C ++ language allows declaring typedef aliases that match the names of existing class types, but only on condition that the alias refers specifically to a type type with the same name.

    In the above example, the first typedef declaration is incorrect from the point of view of C ++.

  4. In the C language, the "unfamiliar" name of the struct type mentioned in the parameter list of a function is a declaration of a new type local to this function. In the list of function parameters, this type can be declared as incomplete, and "declared" to the full type already in the function body.

     /* Тип `struct S` в этой точке не известен */ void foo(struct S *p) { struct S { int a; } s; p = &s; p->a = 5; } 

    In this code, everything is correct from the point of view of the C language: p is of the same type as &s , etc.

    From the point of view of the C ++ language, the reference to the "unfamiliar" name of the struct type in the parameter list of the function is also a declaration of a new type. However, this new type is not local: it is considered to belong to the spanning namespace . Therefore, from the point of view of the C ++ language, the local definition of type S in the above example has nothing to do with the type S mentioned in the parameter list. Assignment p = &s not possible due to the type mismatch. The code is incorrect from the point of view of C ++.

  5. C allows to make definitions of new types inside a cast operator, inside a sizeof operator, in function declarations (return value types and parameter types)

     int a = sizeof(enum E { A, B, C }) + (enum X { D, E, F }) 0; enum E e = B; int b = e + F; 

    The code is incorrect from the point of view of C ++.

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

     /* На уровне файла */ struct S s; struct S { int i; }; 

    The above set of declarations is correct from the point of view of C, but incorrect from the point of view of C ++. The C ++ language absolutely prohibits the definition of objects of incomplete types.

  7. In the C language, many statements create their own implicit scope in addition to the already existing scope in the "body" of this statement, while in C ++ they create a single scope.

    for example

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

    In C, the loop body is a nested scope with respect to the loop header, so this code is correct. In C ++, there is only one scope, which excludes the possibility of the “nested” declaration i .

  8. The C language allows jumping through initialized ads.

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

    The code is incorrect from the point of view of C ++.

  9. In C, nested declarations of struct types put the name of the inner type in the outer (covering) scope.

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

    The code is incorrect from the point of view of C ++.

  10. C allows implicit conversion of pointers of type void *

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

    The code is incorrect from the point of view of C ++.

  11. C supports function declarations without prototypes.

     /* На уровне файла */ void foo(); void bar() { foo(1, 2, 3); } 

    The code is incorrect from the point of view of C ++.

  12. In C, enum values ​​are freely implicitly convertible to int type and vice versa.

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

    The code is incorrect from the point of view of C ++.

  13. Implicitly generated by the C ++ compiler, copy constructors and assignment operators cannot copy volatile objects. In C, copying volatile objects is not a problem.

     struct S { int i; }; volatile struct S v = { 0 }; struct S s = v; 

    The code is incorrect from the point of view of C ++.

  14. In C, string literals are of type char [N] , and in C ++, const char [N] . Even if we assume that the "classic" C ++ as an exception supports the conversion of a string literal to the type char * , this exception only works when it is applied directly to the string literal

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

    The code is incorrect from the point of view of C ++.

  15. C allows the use of "meaningless" storage class specifiers in declarations that do not declare objects

     static struct S { int i; }; 

    The code is incorrect from the point of view of C ++.

    Additionally, it can be noted that in the C language, typedef also formally just one of the storage class specifiers, which allows you to create typedef declarations that do not declare aliases

     typedef struct S { int i; }; 

    C ++ does not allow such typedef declarations.

  16. C allows explicit repetition of cv-qualifiers in ads

     const const int a = 42; 

    The code is incorrect from the point of view of C ++. (C ++ allows a similar "redundant" qualification, but only through intermediate type names: typedef-names, template parameters).

  17. In C, any integer constant expression with a value of 0 can be used as a null pointer constant

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

    This was also the case in C ++ before adopting the C ++ 11 standard. However, in modern C ++, only the literal value 0 (integer literal) of integer values ​​can act as a null pointer constant, but more complex expressions are no longer valid. The above initializations are incorrect in terms of C ++.

  18. In C, you can make a non-defining declaration of an object of type void

     extern void v; 

    (The definition of such an object cannot be done, because void is an incomplete type). In C ++ it is forbidden to make even a non-defining declaration.

  19. In C language, a bit field declared with the int type without an explicit indication of signed or unsigned can be as signed, and unsigned there (defined by the implementation). In C ++, such a bit field is always a significant one.

  20. The C preprocessor is not familiar with literals such as true and false . In the C language, true and false are available only as macros defined in the <stdbool.h> header file. 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 natively recognize the true and false literals, and its #if directive should behave with these literals in the “expected” way.

    This can be a source of incompatibilities when <stdbool.h> not included in C code

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

    This code is obviously incorrect in C ++, and at the same time it can easily compile to C.

  21. In C, an implicit conflict between internal and external binding when declaring the same entity leads to indefinite behavior, and in C ++, such a conflict makes the program ill-formed (erroneous). To arrange such an implicit conflict, you need to build a rather tricky configuration.

     static int a; /* Внутреннее связывание */ void foo(void) { int a; /* Скрывает внешнее static `a`, не имеет связывания */ { extern int a; /* Из-за того, что внешнее static `a` скрыто, объявляет `a` с внешним связыванием. Теперь `a` объявлено и с внешним, и с внутренним связыванием - конфликт */ } } 

    In C ++, this extern declaration is erroneous.

  22. Recursive calls to the main function are allowed in C, but not allowed in C ++.

  • An amendment to clause 8. I do not know what you mean by 'prototype' (in textbooks, it seems, like, 'prototype' is the function declaration. But I will not give my hand for this :-)), but by example - this function declaration without specifying arguments. This declaration disables checking the arguments of the function when calling, which allows you to do all sorts of bad things. In addition, functions can be used without any declaration at all. In this case, the compiler itself derives the prototype from the first function call, and the return value is considered to be int . - Vladimir
  • @Vladimir: You have everything backwards. The prototype in C is the declaration of a "C ++ style" function, i.e. with explicit indication of the number and types of arguments. For example, void foo(int, double); - This is an ad with a prototype. A declaration without specifying arguments (as in my 8) is not a prototype. What kind of "return value is considered an int" you are talking about - it is not clear at all, because any function declaration - what's with the prototype, what without - always explicitly indicates the type of the return value. - AnT
  • I suspect that you are talking about the possibility to call functions that are not declared at all in C89 / 90. But I decided not to mention this feature at all, because it is outdated even in C89 / 90. - AnT
  • Yes, it is from C89, I wrote about them that way - not announced at all. - Vladimir
  • @Vladimir: Well, once again: the ability to call not announced at all is a separate item, which I decided not to include in my list. - AnT

Why do you need it? If you have a C code, then compile it as C. If you need to use C ++, then compile with different compilers, and then link it separately. Do not forget about extern "C" only, otherwise linking will end in error.

UPD

If you think theoretically, then the main problem will probably be not enough strict type checking in C, which C ++ will not tolerate. C ++ is more strict regarding types.

UPD2

And yes, in C you can, for example, call undeclared functions with incorrect arguments. It is clear that C ++ hacks at the same time.

  • 3
    Interest is more theoretical than practical - skegg
  • one
    "Insufficiently strict type checking in C" is actually reduced to the ability to do an implicit conversion from void * to other pointer types and to the compatibility of enum and int in C. In all other respects, the severity of type checking C is not different from the type checking in C ++. - AnT

Good question. Always wrote on with and avoided with ++.

This code (I just don’t remember why, but compiles gcc and works) does not compile g ++.

 #include <stdio.h> #include <stdlib.h> #include <windows.h> #include <time.h> main () { int c, i = 0; char c4[5]; clock_t t = clock(); unsigned short *u = L"12345\0AРђР'\n"; for (i = 0; u[i] != '\n'; i++) printf ("u[%d] = %d (%x)\n",i,u[i],u[i]); printf ("start %d %d\n",CLOCKS_PER_SEC,t); i = 0; while ((c = getch()) != 26 ) { // ^Z text stdin EOF printf ("%d %d\n",CLOCKS_PER_SEC,clock()); if (c >= '0' && c <= '9') { putchar(c); fflush(stdout); c4[i++] = c; } if (c == '\b') { // BS == 8 printf ("\b \b"); fflush(stdout); if (--i < 0) i = 0; continue; } if (c == '\r' || i == 4) { // ENTER or Your 4 digits c4[i] = 0; printf ("\nMy %d digits: %s\n",i,c4); i = 0; } } clock_t t1 = clock(); printf ("%d %d\n",CLOCKS_PER_SEC,t1); } 

MinGW g ++ (GCC) 3.4.5 (mingw-vista special r3) in Windows XP

g ++ -c t1.c

writes:

 t1.c: In function `int main()': t1.c:11: error: invalid conversion from `const wchar_t*' to `short unsigned int*' t1.c:18: error: `getch' was not declared in this scope 

So from the point of view of practice, @ cy6erGn0m told you everything correctly.

  • one
    Yes, most likely, one of the differences is a more rigid policy with implicit type conversion. And rightly so, comrades. gcc for this code simply gives a warning. But if you explicitly cast the type, then g ++ compiles everything normally. But I also try to explicitly type in C. - skegg
  • I have no gcc warnings for this code. - avp
  • 2
    I compiled in Ubuntu gcc 4.4. The fact is that in it the type wchar_t is defined as typedef int wchar_t (in C). Therefore, it warns: warning: assignment from incompatible pointer type In C ++, wchar_t is a built-in type, and C ++ prohibits assigning the value of a pointer of one type to a pointer of another type without an explicit cast. - skegg
  • Yes, 4.4 is more strict. - avp
  • 2
    Positive in Windows, negative in the line - skegg

In addition to the above, it happens that an attempt to compile C code in C++ ends with errors due to malloc , more precisely because of the possibility of a corresponding entry with void* in C and impossibility in C++ :

 // C int* a = malloc(24 * sizeof(int)); // C++ (можно использовать и C-style каст) int* a = static_cast<int*>(malloc(24 * sizeof(int));) 

But in general, yes - compile separately with the appropriate compiler and link. Moreover, for example, the C libraries compiled with gcc , icc and cl are compatible in 99% of cases.

  • Thanks, again, the case rests on a more stringent C ++ policy regarding casting of pointer types. A separate compilation is a favorite entertainment))) - skegg
  • @skegg: The “policies” for casting pointer types are completely identical in C and C ++, except for the possibility of implicit coercion from void * to C. It is this exception that the example is based on. - AnT

Great question. Indeed, C is not a subset of C ++. In addition to the textbook example of casting void * to a typed pointer, there are a number of more rare, but sometimes very unpleasant moments.

Here is the code that is processed without errors by the gcc compiler (including the --pedantic key):

 #include <stdio.h> int plus(); int main() { printf("%d\n", plus(5, 7)); return 0; } int plus(int a, int b) { return a + b; } 

However, when compiling g ++, we have:

 user@linux:~> g++ test.c test.c: In function 'int main()': test.c:7:27: error: too many arguments to function 'int plus()' test.c:3:5: note: declared here 

The C compiler, in accordance with the standard, treats the empty parameter list in the prototype of the plus() function as undefined , the C ++ compiler as empty . Only when calling the C compiler with the -Wall key, -Wall can see the warning.

Of course, any coding style conventions should exclude empty prototypes, since the compiler does not have the ability to check the conformity of type signatures;

  • In which paragraph of the standard is the rule for interpreting an empty parameter list described? I have long wanted to read about this in detail, but I could not find something. - MGNeo

The gcc (currently 7.3) in c ++ mode still does not support some methods for initializing structures / unions / enums that are supported in c mode:

 struct array_s { union { // в объединении C++ может инициализироваться первый элемент struct { uint16_t af; uint16_t bf; }; uint32_t sign; }; uint16_t rows; uint16_t cols; void *data; }; #ifdef __cplusplus struct array_s a = {{{1, 2} }, 100, 200 }; // c++ // struct array_s a = {{{0x20001}}, 100, 200 }; #else struct array_s b = {{ .af = 1, .bf = 2 }, .cols = 200, .rows = 100}; // c #endif 

Since the initialization of the union in works only for the first member, in the above case it is an anonymous structure, it is impossible to perform the initialization of the sign instead of the structure in the classical way neither in s, nor in s ++. However, this can be done in c11, as shown above. In c ++, we get the error:

 error: too many initializers for 'array_s' sorry, unimplemented: non-trivial designated initializers not supported