C i C ++ mają wiele różnic i nie wszystkie poprawne kody C są poprawnymi kodami C ++.
(Przez „prawidłowy” rozumiem standardowy kod ze zdefiniowanym zachowaniem, tj. Nie jest specyficzny dla implementacji / niezdefiniowany / itp.)
Czy istnieje scenariusz, w którym fragment kodu poprawny zarówno w C, jak i C ++ powodowałby inne zachowanie po skompilowaniu ze standardowym kompilatorem w każdym języku?
Aby było to rozsądne / przydatne porównanie (staram się nauczyć czegoś praktycznie przydatnego, a nie próbować znaleźć oczywiste luki w pytaniu), załóżmy:
- Nic nie jest związane z preprocesorem (co oznacza brak hacków
#ifdef __cplusplus
, pragm itp.) - Wszystko zdefiniowane w implementacji jest takie samo w obu językach (np. Ograniczenia liczbowe itp.)
- Porównujemy stosunkowo nowe wersje każdego standardu (np. C ++ 98 i C90 lub nowszy)
Jeśli wersje mają znaczenie, proszę wspomnieć, które wersje każdego z nich mają inne zachowanie.
Odpowiedzi:
Następujące, poprawne w C i C ++, (najprawdopodobniej) spowodują różne wartości
i
w C i C ++:Zobacz Rozmiar znaku („a”) w C / C ++, aby uzyskać wyjaśnienie różnicy.
Kolejny z tego artykułu :
źródło
struct
przed nazwami struktur.struct sz { int i[2];};
oznaczałoby to, że C i C ++ muszą generować różne wartości. (Podczas gdy DSP z sizeof (int) == 1, może wygenerować tę samą wartość).Oto przykład, który wykorzystuje różnicę między wywołaniami funkcji i deklaracjami obiektów w C i C ++, a także fakt, że C90 pozwala na wywoływanie funkcji niezadeklarowanych:
W C ++ nic to nie wydrukuje, ponieważ tymczasowe
f
jest tworzone i niszczone, ale w C90 będzie drukowane,hello
ponieważ funkcje można wywoływać bez zadeklarowania.W przypadku, gdy zastanawiasz się nad
f
dwukrotnym użyciem nazwy , standardy C i C ++ wyraźnie na to zezwalają i aby stworzyć obiekt, o którym musisz powiedzieć,struct f
aby ujednoznacznić, jeśli chcesz strukturę, lub zrezygnować,struct
jeśli chcesz funkcji.źródło
W przypadku C ++ vs. C90 istnieje co najmniej jeden sposób uzyskania różnych zachowań, które nie są zdefiniowane w implementacji. C90 nie ma komentarzy jednowierszowych. Przy odrobinie staranności możemy go użyć do stworzenia wyrażenia o zupełnie innych wynikach w C90 i C ++.
W C ++ wszystko od
//
końca do końca jest komentarzem, więc działa to tak:Ponieważ C90 nie ma komentarzy jednowierszowych, tylko
/* comment */
komentarz jest. Pierwsza/
i2
obie są częściami inicjalizacji, więc dochodzi do:Tak więc poprawny kompilator C ++ da 13, ale ściśle poprawny kompilator C90 8. Oczywiście, właśnie wybrałem tutaj dowolne liczby - możesz użyć innych liczb, które uważasz za stosowne.
źródło
2
, odczytałby jako10 / + 3
prawidłowy (unary +).C90 vs. C ++ 11 (
int
vs.double
):W C
auto
oznacza zmienną lokalną. W C90 można pominąć typ zmiennej lub funkcji. Domyślnie jest toint
. W C ++ 11auto
oznacza coś zupełnie innego, mówi kompilatorowi, aby wywnioskował typ zmiennej z wartości użytej do jej zainicjowania.źródło
auto
?int
domyślnie. To jest sprytne! +1int
.int
. Mimo to, w prawdziwym świecie, gdzie jest mnóstwo starszego kodu, a lider rynku nadal nie wdrożył C99 i nie ma na to zamiaru, mówienie o „przestarzałej wersji C” jest absurdalne.Kolejny przykład, o którym jeszcze nie wspominałem, ten podkreślający różnicę w preprocesorze:
To wypisuje „fałsz” w C i „prawda” w C ++ - W C każde niezdefiniowane makro ma wartość 0. W C ++ istnieje 1 wyjątek: „prawda” oznacza 1.
źródło
#define true false
ಠ_ಠZgodnie ze standardem C ++ 11:
za. Operator przecinka wykonuje konwersję wartości lvalue na rvalue w C, ale nie w C ++:
W C ++ wartość tego wyrażenia będzie wynosić 100, aw C będzie to
sizeof(char*)
.b. W C ++ typem modułu wyliczającego jest jego wyliczenie. W C typ modułu wyliczającego to int.
Oznacza to, że
sizeof(int)
może nie być równysizeof(E)
.do. W C ++ funkcja zadeklarowana z pustą listą parametrów nie przyjmuje żadnych argumentów. W C pusta lista parametrów oznacza, że liczba i typ parametrów funkcji jest nieznany.
źródło
sizeof(char*)
może wynosić 100, w którym to przypadku pierwszy przykład dałby takie same obserwowalne zachowanie w C i C ++ (tj. chociaż metoda uzyskiwanias
byłaby inna,s
ostatecznie byłaby równa 100). OP wspomniał, że tego rodzaju zachowanie zdefiniowane w ramach implementacji było w porządku, ponieważ chciał po prostu uniknąć odpowiedzi prawników językowych, więc pierwszy z nich jest w porządku z jego wyjątkiem. Ale drugi jest w każdym razie dobry.char arr[sizeof(char*)+1]; int s = sizeof(0, arr);
void *arr[100]
. W tym przypadku element ma taki sam rozmiar jak wskaźnik do tego samego elementu, więc dopóki są 2 lub więcej elementów, tablica musi być większa niż adres pierwszego elementu.Ten program drukuje
1
w C ++ i0
C:Dzieje się tak, ponieważ
double abs(double)
w C ++ występuje przeciążenie, więcabs(0.6)
wraca,0.6
podczas gdy w C zwraca z0
powodu niejawnej konwersji podwójnej na int przed wywołaniemint abs(int)
. W C musisz używaćfabs
do pracydouble
.źródło
stdlib.h
definiuje tylkoabs(int)
iabs(long)
; wersjaabs(double)
jest zadeklarowana przezmath.h
. Więc ten program może nadal wywoływaćabs(int)
wersję. Jest to szczegół implementacji, czystdlib.h
również powodujemath.h
włączenie. (Myślę, że byłby to błąd, gdybyabs(double)
został wywołany, ale inne aspektymath.h
nie zostały uwzględnione).<math.h>
obejmuje również dodatkowe przeciążenia; w praktyce okazuje się, że wszystkie główne kompilatory nie uwzględniają tych przeciążeń, chyba że<cmath>
użyta zostanie forma .W C drukuje to bez względu na wartość
sizeof(int)
obecnego systemu, który jest zwykle4
w większości powszechnie używanych dziś systemów.W C ++ musi to być wydrukowane 1.
źródło
%d
nie jest właściwym specyfikatorem formatusize_t
.Kolejna
sizeof
pułapka: wyrażenia logiczne.Jest równy
sizeof(int)
w C, ponieważ wyrażenie jest typuint
, ale zwykle ma 1 w C ++ (choć nie jest to wymagane). W praktyce są prawie zawsze różne.źródło
!
powinny być wystarczająco dlabool
.sizeof(0)
jest4
w C i C ++, ponieważ0
jest wartością całkowitą.sizeof(!0)
jest4
w C i1
C ++. Logiczne NIE działa na operandach typu bool. Jeśli wartość int0
jest niejawnie konwertowana nafalse
(wartość bool), to jest odwracana, w wyniku czegotrue
. Zarównotrue
ifalse
są rvalues bool w C ++ isizeof(bool)
jest1
. Jednak w C!0
ocenia się na1
, co jest wartością typu int. Język programowania C domyślnie nie ma typu danych bool.Język programowania C ++ (wydanie trzecie) podaje trzy przykłady:
sizeof („a”), jak wspomniał @Adam Rosenfield;
//
komentarze używane do tworzenia ukrytego kodu:Struktury itp. Ukrywają rzeczy poza zasięgami, jak w twoim przykładzie.
źródło
Stary kasztan, który zależy od kompilatora C, nie rozpoznaje komentarzy na końcu C ++ ...
źródło
Kolejny wymieniony przez C ++ Standard:
źródło
x
na górze. myślałem, że powiedziałeś „tablicaa
”.Funkcje wbudowane w C domyślnie mają zasięg zewnętrzny, gdzie nie działają tak jak w C ++.
Kompilacja dwóch następujących plików razem spowoduje wydrukowanie „Jestem inline” w przypadku GNU C, ale nic w przypadku C ++.
Plik 1
Plik 2
Ponadto C ++ domyślnie traktuje dowolne
const
globalne,static
chyba że jest to jawnie zadeklarowaneextern
, w przeciwieństwie do C, w którymextern
domyślna jest wartość C.źródło
extern
które zostało tutaj wykazane?struct fun
vsfn
) i nie ma nic wspólnego z tym, czy funkcja jest wbudowana. Wynik jest identyczny, jeśli usunieszinline
kwalifikator.inline
został dodany dopiero w C99, ale w C99fun()
nie można go wywołać bez prototypu w zakresie. Zakładam więc, że ta odpowiedź dotyczy tylko GNU C.Zwraca kod zakończenia 0 w C ++ lub 3 w C.
Tej sztuczki można by prawdopodobnie użyć do zrobienia czegoś bardziej interesującego, ale nie mogłem wymyślić dobrego sposobu na stworzenie konstruktora, który byłby przyjemny dla C. Próbowałem zrobić podobnie nudny przykład z konstruktorem kopiowania, który pozwoliłby na argument być przekazywane, choć w raczej nieprzenośny sposób:
VC ++ 2005 odmówił jednak skompilowania tego w trybie C ++, narzekając na to, jak redefiniowano „kod wyjścia”. (Wydaje mi się, że jest to błąd kompilatora, chyba że nagle zapomniałem, jak programować.) Został zakończony z kodem wyjścia procesu 1, gdy został skompilowany jako C.
źródło
exit(code)
najwyraźniej jest poprawną deklaracją zmiennejcode
typuexit
. (Zobacz „najbardziej dokuczliwa analiza”, która jest inną, ale podobną kwestią).Ten program wypisuje
128
(32 * sizeof(double)
) po kompilacji za pomocą kompilatora C ++ i4
po kompilacji za pomocą kompilatora C.Wynika to z faktu, że C nie ma pojęcia rozdzielczości zakresu. W C struktury zawarte w innych strukturach wchodzą w zakres struktury zewnętrznej.
źródło
32*sizeof(double)
raczej niż 32 :))size_t
pomocą%d
Nie zapomnij o różnicy między globalnymi przestrzeniami nazw C i C ++. Załóżmy, że masz plik foo.cpp
i foo2.c
Załóżmy teraz, że masz pliki main.c i main.cpp, które wyglądają tak:
Po skompilowaniu jako C ++ użyje symbolu w globalnej przestrzeni nazw C ++; w C użyje C:
źródło
foo
). Nie ma oddzielnych „globalnych przestrzeni nazw”.Jest to dość szczególne, ponieważ jest poprawne w C ++ oraz w C99, C11 i C17 (choć opcjonalne w C11, C17); ale nie dotyczy C89.
W C99 + tworzy tablicę o zmiennej długości, która ma swoje specyficzne cechy w porównaniu z normalnymi tablicami, ponieważ ma typ środowiska wykonawczego zamiast typu kompilacji i
sizeof array
nie jest wyrażeniem stałym w liczbach całkowitych w C. W C ++ typ jest całkowicie statyczny.Jeśli spróbujesz dodać tutaj inicjator:
jest poprawny w C ++, ale nie w C, ponieważ tablice o zmiennej długości nie mogą mieć inicjatora.
źródło
Dotyczy to wartości i wartości w C i C ++.
W języku programowania C zarówno operatory wstępnej, jak i końcowej operacji zwracają wartości, a nie wartości. Oznacza to, że nie mogą znajdować się po lewej stronie
=
operatora przypisania. Obie te instrukcje spowodują błąd kompilatora w C:Jednak w C ++ operator wstępnej inkrementacji zwraca wartość , podczas gdy operator po inkrementacji zwraca wartość. Oznacza to, że wyrażenie z operatorem wstępnego przyrostu można umieścić po lewej stronie
=
operatora przypisania!Dlaczego tak jest? Post-increment inkrementuje zmienną i zwraca zmienną taką, jaka była wcześniej inkrementacją. To właściwie tylko wartość. Poprzednia wartość zmiennej a jest tymczasowo kopiowana do rejestru, a następnie jest zwiększana. Ale poprzednia wartość a jest zwracana przez wyrażenie, jest to wartość. Nie reprezentuje już bieżącej zawartości zmiennej.
Wstępna inkrementacja najpierw inkrementuje zmienną, a następnie zwraca zmienną taką, jaka była po inkrementacji. W takim przypadku nie musimy przechowywać starej wartości zmiennej w rejestrze tymczasowym. Po prostu pobieramy nową wartość zmiennej po jej zwiększeniu. Tak więc przyrost wstępny zwraca wartość, zwraca samą zmienną. Możemy użyć przypisania tej wartości do czegoś innego, to jest jak poniższa instrukcja. Jest to domyślna konwersja wartości do wartości.
Ponieważ wstępny przyrost zwraca wartość, możemy również mu coś przypisać. Poniższe dwa stwierdzenia są identyczne. W drugim zadaniu najpierw zwiększa się a, a następnie nową wartość zastępuje 2.
źródło
Puste struktury mają rozmiar 0 w C i 1 w C ++:
źródło