C et C ++ ont de nombreuses différences, et tous les codes C valides ne sont pas des codes C ++ valides.
(Par "valide", je veux dire du code standard avec un comportement défini, c'est-à-dire non spécifique à l'implémentation / non défini / etc.)
Existe-t-il un scénario dans lequel un morceau de code valide à la fois en C et C ++ produirait un comportement différent lorsqu'il est compilé avec un compilateur standard dans chaque langue?
Pour en faire une comparaison raisonnable / utile (j'essaie d'apprendre quelque chose d'utile, pas d'essayer de trouver des failles évidentes dans la question), supposons:
- Rien lié au préprocesseur (ce qui signifie pas de hacks avec
#ifdef __cplusplus
, pragmas, etc.) - Tout ce qui est défini par l'implémentation est le même dans les deux langues (par exemple, limites numériques, etc.)
- Nous comparons des versions raisonnablement récentes de chaque norme (par exemple, C ++ 98 et C90 ou version ultérieure).
Si les versions sont importantes, veuillez indiquer quelles versions de chacune produisent un comportement différent.
Réponses:
Les éléments suivants, valides en C et C ++, entraîneront (très probablement) des valeurs différentes
i
en C et C ++:Voir Taille du caractère ('a') en C / C ++ pour une explication de la différence.
Un autre de cet article :
la source
struct
noms de structure avant.struct sz { int i[2];};
signifierait que C et C ++ doivent produire des valeurs différentes. (Alors qu'un DSP avec sizeof (int) == 1, pourrait produire la même valeur).Voici un exemple qui tire parti de la différence entre les appels de fonction et les déclarations d'objet en C et C ++, ainsi que du fait que C90 autorise l'appel de fonctions non déclarées:
En C ++, cela n'imprimera rien car un temporaire
f
est créé et détruit, mais en C90, il s'imprimerahello
car les fonctions peuvent être appelées sans avoir été déclarées.Au cas où vous vous poseriez des questions sur le nom
f
utilisé deux fois, les normes C et C ++ le permettent explicitement, et pour faire un objet que vous devez direstruct f
pour lever l'ambiguïté si vous voulez la structure, ou laisser de côtéstruct
si vous voulez la fonction.la source
Pour C ++ et C90, il existe au moins un moyen d'obtenir un comportement différent qui n'est pas défini par l'implémentation. C90 n'a pas de commentaires sur une seule ligne. Avec un peu de soin, nous pouvons l'utiliser pour créer une expression avec des résultats entièrement différents en C90 et en C ++.
En C ++, tout depuis la
//
fin de la ligne est un commentaire, donc cela fonctionne comme:Étant donné que C90 n'a pas de commentaires sur une seule ligne, seul le
/* comment */
est un commentaire. Le premier/
et le2
sont les deux parties de l'initialisation, il en résulte donc:Ainsi, un compilateur C ++ correct donnera 13, mais un compilateur C90 strictement correct 8. Bien sûr, je viens de choisir des nombres arbitraires ici - vous pouvez utiliser d'autres nombres comme bon vous semble.
la source
2
, il se lirait comme10 / + 3
valide (unaire +).C90 contre C ++ 11 (
int
contredouble
):En C
auto
signifie variable locale. En C90, il est possible d'omettre le type de variable ou de fonction. Il est par défautint
. En C ++ 11auto
signifie quelque chose de complètement différent, il indique au compilateur de déduire le type de la variable à partir de la valeur utilisée pour l'initialiser.la source
auto
?int
par défaut. C'est intelligent! +1int
.int
. Pourtant, dans le monde réel, où il y a des tonnes de code hérité et où le leader du marché n'a toujours pas implémenté C99 et n'a aucune intention de le faire, parler d'une "version obsolète de C" est absurde.Un autre exemple que je n'ai pas encore vu mentionné, celui-ci mettant en évidence une différence de préprocesseur:
Cela affiche "false" en C et "true" en C ++ - En C, toute macro non définie est évaluée à 0. En C ++, il y a 1 exception: "true" est évalué à 1.
la source
#define true false
ಠ_ಠSelon la norme C ++ 11:
une. L'opérateur virgule effectue une conversion de lvalue en rvalue en C mais pas en C ++:
En C ++, la valeur de cette expression sera 100 et en C, ce sera
sizeof(char*)
.b. En C ++, le type d'énumérateur est son énumération. En C, le type d'énumérateur est int.
Cela signifie que
sizeof(int)
peut ne pas être égal àsizeof(E)
.c. En C ++, une fonction déclarée avec une liste de paramètres vide ne prend aucun argument. En C, une liste de paramètres vide signifie que le nombre et le type de paramètres de fonction sont inconnus.
la source
sizeof(char*)
pourrait être 100, auquel cas le premier exemple produirait le même comportement observable en C et C ++ (c.-à-d. bien que la méthode d'obtentions
soit différente,s
finirait par être 100). Le PO a mentionné que ce type de comportement défini par la mise en œuvre était bien car il voulait simplement éviter les réponses des juristes linguistiques, donc le premier est bien par son exception. Mais le second est bon en tout cas.char arr[sizeof(char*)+1]; int s = sizeof(0, arr);
void *arr[100]
. Dans ce cas, un élément a la même taille qu'un pointeur vers le même élément, donc tant qu'il y a 2 éléments ou plus, le tableau doit être plus grand que l'adresse de son premier élément.Ce programme imprime
1
en C ++ et0
en C:Cela se produit car il y a
double abs(double)
surcharge en C ++, doncabs(0.6)
retourne0.6
alors qu'en C il retourne à0
cause de la conversion implicite double en entier avant l'appelint abs(int)
. En C, vous devez utiliserfabs
pour travailler avecdouble
.la source
stdlib.h
définit uniquementabs(int)
etabs(long)
; la versionabs(double)
est déclarée parmath.h
. Donc, ce programme peut toujours appeler laabs(int)
version. Il s'agit d'un détail de mise en œuvre qui doitstdlib.h
égalementmath.h
être inclus. (Je pense que ce serait un bug s'ilabs(double)
était appelé, mais d'autres aspecsmath.h
n'étaient pas inclus).<math.h>
inclut également les surcharges supplémentaires; en pratique, il s'avère que tous les principaux compilateurs n'incluent pas ces surcharges à moins que le formulaire ne<cmath>
soit utilisé.En C, cela imprime quelle que soit la valeur de
sizeof(int)
est sur le système actuel, qui est généralement4
dans la plupart des systèmes couramment utilisés aujourd'hui.En C ++, cela doit afficher 1.
la source
%d
n'est pas le bon spécificateur de format poursize_t
.Autre
sizeof
piège: les expressions booléennes.Il est égal à
sizeof(int)
C, car l'expression est de typeint
, mais est généralement 1 en C ++ (bien qu'il ne soit pas obligatoire de l'être). En pratique, ils sont presque toujours différents.la source
!
devrait suffire pour unbool
.sizeof(0)
est4
à la fois en C et C ++ car0
est une valeur entière.sizeof(!0)
est4
en C et1
en C ++. Logical NOT fonctionne sur des opérandes de type bool. Si la valeur int est0
elle est implicitement convertie enfalse
(une valeur booléenne), alors elle est inversée, résultant entrue
. Les deuxtrue
etfalse
sont des valeurs booléennes en C ++ etsizeof(bool)
is1
. Cependant en C est!0
évalué à1
, qui est une valeur r de type int. Le langage de programmation C n'a pas de type de données bool par défaut.Le langage de programmation C ++ (3e édition) donne trois exemples:
sizeof ('a'), comme @Adam Rosenfield l'a mentionné;
//
commentaires utilisés pour créer du code caché:Structures, etc. masquant des éléments dans des portées, comme dans votre exemple.
la source
Un vieux châtaignier qui dépend du compilateur C, ne reconnaissant pas les commentaires de fin de ligne C ++ ...
la source
Un autre répertorié par la norme C ++:
la source
x
en haut. je pensais que vous avez dit "le tableaua
".Les fonctions en ligne en C ont par défaut une portée externe, contrairement à celles de C ++.
La compilation des deux fichiers suivants afficherait le "Je suis en ligne" dans le cas de GNU C mais rien pour C ++.
Fichier 1
Fichier 2
En outre, C ++ traite implicitement tout
const
global commestatic
s'il n'est pas déclaré explicitementextern
, contrairement à C dans lequelextern
est la valeur par défaut.la source
extern
cela est démontré ici?struct fun
vsfn
) et n'a rien à voir si la fonction est en ligne. Le résultat est identique si vous supprimez leinline
qualificatif.inline
n'a pas été ajouté avant C99, mais en C99,fun()
il ne peut pas être appelé sans un prototype de portée. Je suppose donc que cette réponse ne s'applique qu'à GNU C.Renvoie avec un code de sortie de 0 en C ++ ou 3 en C.
Cette astuce pourrait probablement être utilisée pour faire quelque chose de plus intéressant, mais je ne pouvais pas penser à un bon moyen de créer un constructeur qui serait acceptable pour C. J'ai essayé de faire un exemple similaire ennuyeux avec le constructeur de copie, qui laisserait un argument être adopté, bien que d'une manière plutôt non portable:
VC ++ 2005 a refusé de compiler cela en mode C ++, cependant, se plaignant de la façon dont le "code de sortie" a été redéfini. (Je pense que c'est un bogue du compilateur, sauf si j'ai soudainement oublié comment programmer.) Il s'est terminé avec un code de sortie de processus de 1 quand il est compilé en C.
la source
exit(code)
est une déclaration valide d'une variablecode
de typeexit
, apparemment. (Voir "analyse la plus vexante", qui est un problème différent mais similaire).Ce programme affiche
128
(32 * sizeof(double)
) lorsqu'il est compilé à l'aide d'un compilateur C ++ et4
lorsqu'il est compilé à l'aide d'un compilateur C.C'est parce que C n'a pas la notion de résolution de portée. En C, les structures contenues dans d'autres structures sont mises dans le cadre de la structure externe.
la source
32*sizeof(double)
plutôt que 32 cependant :))size_t
avec%d
N'oubliez pas la distinction entre les espaces de noms globaux C et C ++. Supposons que vous ayez un foo.cpp
et un foo2.c
Supposons maintenant que vous ayez un main.c et un main.cpp qui ressemblent tous les deux à ceci:
Une fois compilé en C ++, il utilisera le symbole dans l'espace de noms global C ++; en C il utilisera le C:
la source
foo
). Il n'y a pas d '"espaces de noms globaux" séparés.C'est assez particulier en ce sens qu'il est valide en C ++ et en C99, C11 et C17 (bien que facultatif en C11, C17); mais non valide en C89.
En C99 +, il crée un tableau de longueur variable, qui a ses propres particularités par rapport aux tableaux normaux, car il a un type d'exécution au lieu du type au moment de la compilation, et
sizeof array
n'est pas une expression constante entière en C. En C ++, le type est entièrement statique.Si vous essayez d'ajouter un initialiseur ici:
est C ++ valide mais pas C, car les tableaux de longueur variable ne peuvent pas avoir d'initialiseur.
la source
Cela concerne les lvalues et les rvalues en C et C ++.
Dans le langage de programmation C, les opérateurs de pré-incrémentation et de post-incrémentation renvoient rvalues, pas lvalues. Cela signifie qu'ils ne peuvent pas être sur le côté gauche de l'
=
opérateur d'affectation. Ces deux instructions donneront une erreur de compilation en C:En C ++ cependant, l'opérateur de pré-incrémentation renvoie une valeur l , tandis que l'opérateur de post-incrémentation renvoie une valeur r. Cela signifie qu'une expression avec l'opérateur de pré-incrémentation peut être placée sur le côté gauche de l'
=
opérateur d'affectation!Maintenant, pourquoi en est-il ainsi? Le post-incrément incrémente la variable, et il renvoie la variable telle qu'elle était avant l'incrémentation ne se produise. C'est en fait juste une valeur r. L'ancienne valeur de la variable a est copiée dans un registre comme temporaire, puis a est incrémentée. Mais l'ancienne valeur de a est renvoyée par l'expression, c'est une valeur r. Il ne représente plus le contenu actuel de la variable.
La pré-incrémentation incrémente d'abord la variable, puis elle renvoie la variable telle qu'elle est devenue après l'incrémentation. Dans ce cas, nous n'avons pas besoin de stocker l'ancienne valeur de la variable dans un registre temporaire. Nous récupérons simplement la nouvelle valeur de la variable après son incrémentation. Ainsi, le pré-incrément renvoie une valeur l, il renvoie la variable a elle-même. Nous pouvons utiliser assigner cette valeur à quelque chose d'autre, c'est comme l'instruction suivante. Il s'agit d'une conversion implicite de lvalue en rvalue.
Puisque le pré-incrément renvoie une valeur l, nous pouvons également lui attribuer quelque chose. Les deux déclarations suivantes sont identiques. Dans la deuxième affectation, d'abord a est incrémenté, puis sa nouvelle valeur est remplacée par 2.
la source
Les structures vides ont une taille 0 en C et 1 en C ++:
la source