Dlaczego sizeof (x ++) nie zwiększa x?

505

Oto kod skompilowany w oknach dev c ++:

#include <stdio.h>

int main() {
    int x = 5;
    printf("%d and ", sizeof(x++)); // note 1
    printf("%d\n", x); // note 2
    return 0;
}

Spodziewam xsię, że będę mieć 6 lat po wykonaniu uwagi 1 . Jednak wynik jest następujący:

4 and 5

Czy ktoś może wyjaśnić, dlaczego xnie zwiększa się po uwadze 1 ?

Neigyl R. Noval
źródło
37
Chciałbym zauważyć, że DevC ++ używa bardzo starego, przestarzałego kompilatora, możesz chcieć uaktualnić do nowszego IDE, np. Codeblocks Eclipse lub Visual Studio
Tom J Nowell
4
++ x daje taki sam wynik jak x ++, cygwin i gcc 3.4.4.
yehnan
6
@Tim Pletzcker Ponieważ pierwsza wartość to rozmiar zmiennej, a nie sama zmienna.
Surfbutler
Moja odpowiedź została dodana z powodu połączenia z tą, moja odpowiedź pokazuje, w jaki sposób można uzyskać ocenę w czasie wykonywania, w przypadku VLAsktórej żadna z pozostałych osób tego nie robi.
Shafik Yaghmour
2
tego rodzaju pisania należy unikać, ponieważ może to powodować niepożądane skutki uboczne. Twoje pytanie jest już jednym z nich.
user666412,

Odpowiedzi:

537

Ze standardu C99 (nacisk jest mój)

6.5.3.4/2

Operator sizeof zwraca rozmiar (w bajtach) swojego operandu, który może być wyrażeniem lub nawiasową nazwą typu. Rozmiar jest określany na podstawie typu operandu. Wynikiem jest liczba całkowita. Jeśli typ argumentu jest tablicą o zmiennej długości, argument jest oceniany; w przeciwnym razie operand nie jest analizowany, a wynik jest stałą całkowitą.

pmg
źródło
51
„Jeśli typem argumentu jest tablica o zmiennej długości, argument jest oceniany” wow! Nigdy nie zdawałem sobie z tego sprawy
Kos,
6
co rozumiesz przez typ tablicy o zmiennej długości? Czy to znaczy, że operand jest tablicą? Kod w tym przypadku nie jest tablicą. Czy możesz mi to wyjaśnić?
Neigyl R. Noval
37
Tablica o zmiennej długości to tablica deklarowana, której rozmiar jest wartością nieznaną podczas kompilacji, na przykład jeśli czytasz Nze standardowego wejścia i make int array[N]. Jest to jedna z funkcji C99, niedostępna w C ++.
Kos,
21
@LegendofCage, w szczególności oznaczałoby to, że w czymś takim sizeof(int[++x])(naprawdę zły pomysł, tak czy inaczej) ++można to ocenić.
Jens Gustedt
3
@Joe Wreschnig: To jest oceniany przez gcc, clanga na ideone.com/Pf7iF
JFS
190

sizeofjest operatorem czasu kompilacji , więc w momencie kompilacji sizeofi jego operand zostają zastąpione wartością wynikową. Operand nie analizowany (z wyjątkiem gdy jest to zmienna długość tablicy) wcale; liczy się tylko rodzaj wyniku.

short func(short x) {  // this function never gets called !!
   printf("%d", x);    // this print never happens
   return x;
}

int main() {
   printf("%d", sizeof(func(3))); // all that matters to sizeof is the 
                                  // return type of the function.
   return 0;
}

Wynik:

2

ponieważ shortzajmuje 2 bajty na moim komputerze.

Zmiana typu zwracanej funkcji na double:

double func(short x) {
// rest all same

da 8jako wynik.

kodaddict
źródło
12
Tylko czasami - jeśli to możliwe, czas kompilacji.
Martin Beckett,
10
-1, ponieważ jest to sprzeczne z zaakceptowaną (i poprawną) odpowiedzią i nie przytacza standardu.
sam hocevar
1
Przyjemną zaletą kompilacji rozdzielczości operatora sizeof () podczas pracy z łańcuchami jest duża zaleta. Jeśli masz ciąg, który jest inicjowany jako ciąg cytowany, zamiast strlen (), gdzie tablica znaków zawierająca ciąg musi zostać przeskanowana pod kątem terminatora zerowego w czasie wykonywania, rozmiarof (ciąg_cytowany) jest znany w czasie kompilacji, i dlatego w czasie wykonywania. To drobiazg, ale jeśli użyjesz cytowanego ciągu w pętli miliony i miliony razy, ma to znaczącą różnicę w wydajności.
user2548100,
Jeśli naprawdę użyjesz go miliony razy w pętli, czy nie byłoby o wiele rozsądniej brać pod uwagę obliczenia długości poza pętlą? Mam nadzieję, że w twoim kodzie nie ma milionów różnych stałych zapisanych na stałe. : -o
Veky
47

sizeof(foo) naprawdę ciężko próbuje odkryć rozmiar wyrażenia w czasie kompilacji:

6.5.3.4:

Operator sizeof zwraca rozmiar (w bajtach) swojego operandu, który może być wyrażeniem lub nawiasową nazwą typu. Rozmiar jest określany na podstawie typu operandu. Wynikiem jest liczba całkowita. Jeśli typ argumentu jest tablicą o zmiennej długości, argument jest oceniany; w przeciwnym razie operand nie jest analizowany, a wynik jest stałą całkowitą.

W skrócie: tablice o zmiennej długości, uruchamiane w czasie wykonywania. (Uwaga: Tablice o zmiennej długości są specyficzną cechą - nie tablice przypisane do malloc(3).) W przeciwnym razie tylko typ obliczany jest wyrażenia i to w czasie kompilacji.

Sarnold
źródło
33

sizeofjest wbudowanym operatorem czasu kompilacji i nie jest funkcją. Staje się to bardzo jasne w przypadkach, gdy można go użyć bez nawiasu:

(sizeof x)  //this also works
hugomg
źródło
1
Ale jak to odpowiedź na pytanie?
Sebastian Mach
5
@phresnel: Ma to na celu wyjaśnienie, że sizeof jest „dziwny” i nie podlega regułom normalnych funkcji.
Mimo
sizeofOperator nie operator kompilacji, trzeba tylko dać mu VLA do tego dojść.
paxdiablo
21

Uwaga

Ta odpowiedź została połączona z duplikatu, co wyjaśnia późną datę.

Oryginalny

Z wyjątkiem tablic o zmiennej długości sizeof nie ocenia swoich argumentów. Widzimy to z sekcji standardowej projektu C99. 6.5.3.4 Rozmiar operatora ust. 2, który mówi:

Operator sizeof zwraca rozmiar (w bajtach) swojego operandu, który może być wyrażeniem lub nawiasową nazwą typu. Rozmiar jest określany na podstawie typu operandu. Wynikiem jest liczba całkowita. Jeśli typ argumentu jest tablicą o zmiennej długości, argument jest oceniany; w przeciwnym razie operand nie jest analizowany, a wynik jest stałą całkowitą.

Komentarz ( teraz usunięty ) pytał, czy coś takiego może ocenić w czasie wykonywania:

sizeof( char[x++]  ) ;

i rzeczywiście zadziałałoby, coś takiego również działałoby ( Zobacz ich obu na żywo ):

sizeof( char[func()]  ) ;

ponieważ oba są tablicami o zmiennej długości. Chociaż w żadnym z nich nie widzę praktycznego zastosowania.

Uwaga: tablice o zmiennej długości są omówione w paragrafie 4 projektu standardowej sekcji C99 6.7.5.2 deklaratorów tablic sekcji :

[...] Jeśli rozmiar jest wyrażeniem stałym liczby całkowitej, a typ elementu ma znany stały rozmiar, typ tablicy nie jest typem tablicy o zmiennej długości; w przeciwnym razie typ tablicy jest typem tablicy o zmiennej długości.

Aktualizacja

W C11 odpowiedź zmienia się w przypadku VLA, w niektórych przypadkach nie jest określone, czy wyrażenie wielkości jest oceniane, czy nie. Z sekcji 6.7.6.2 Deklaratory tablicy, która mówi:

[...] Jeżeli wyrażenie wielkości jest częścią argumentu operatora sizeof, a zmiana wartości wyrażenia rozmiaru nie wpłynie na wynik operatora, nie jest określone, czy wyrażenie wielkości jest oceniane.

Na przykład w takim przypadku ( zobacz na żywo ):

sizeof( int (*)[x++] )
Shafik Yaghmour
źródło
1
Ważną rzeczą do zapamiętania jest to, że w większości przypadków sizeofjest to makro - nie tworzy kodu, ale wstępnie oblicza oczekiwaną wartość i umieszcza ją bezpośrednio w kodzie. Zauważ, że było to jedyne zachowanie do C99, ponieważ VBA nie istniały (nigdy tak naprawdę nie słyszałem o nich aż do tej odpowiedzi, wierzcie lub nie!)
Corley Brigman
W jaki sposób użyłby sizeof (char[x++]);wartości xdo czegokolwiek innego niż określenie wartości wyrażenia x++i nowej wartości x, które są normalne dla tego operatora?
supercat
@alk ... err, tak, oczywiście miałem na myśli „VLA” :) Shafik - dlaczego mieliby to oceniać w czasie wykonywania? tak jak powiedziałem, nigdy nie widziałem VLA, ale ich typy są znane w czasie kompilacji, prawda?
Corley Brigman
@CorleyBrigman żwawą odpowiedzią byłoby b / c standard mówi tak, ale powodem jest b / c nie znamy rozmiaru tablicy w czasie kompilacji, więc musimy ocenić wyrażenie w czasie wykonywania. VLA są interesującym tematem, oto dwa posty, które mam na ich temat tu i tutaj .
Shafik Yaghmour
@Shafik - ahh, ok, chyba mylę się, że nie zdawałem sobie sprawy, że char[x++]to VLA. char*dla moich nieznanych oczu wygląda to skutecznie .
Corley Brigman
11

Ponieważ operand sizeofoperatora nie jest oceniany, możesz to zrobić:

int f(); //no definition, which means we cannot call it

int main(void) {
        printf("%d", sizeof(f()) );  //no linker error
        return 0;
}

Demo online: http://ideone.com/S8e2Y

Oznacza to, że nie musisz definiować funkcji, fjeśli jest używana sizeoftylko w. Ta technika jest najczęściej używana w metaprogramowaniu szablonów C ++, podobnie jak w C ++, operandziesizeof nie jest oceniany.

Dlaczego to działa? Działa, ponieważ sizeofoperator nie działa na wartości , zamiast tego działa na typie wyrażenia. Więc kiedy piszesz sizeof(f()), działa na typie wyrażenia f(), które jest niczym innym jak zwróconym typem funkcji f. Typ zwracany jest zawsze taki sam, bez względu na wartość, jaką zwróciłaby funkcja, gdyby faktycznie się wykonała.

W C ++ możesz nawet to:

struct A
{
  A(); //no definition, which means we cannot create instance!
  int f(); //no definition, which means we cannot call it
};

int main() {
        std::cout << sizeof(A().f())<< std::endl;
        return 0;
}

Wygląda jednak na to sizeof, że najpierw tworzę instancję A, pisząc A(), a następnie wywołując funkcję fw instancji, pisząc A().f(), ale nic takiego się nie dzieje.

Demo: http://ideone.com/egPMi

Oto kolejny temat, który wyjaśnia kilka innych interesujących właściwości sizeof:

Nawaz
źródło
10

Wykonanie nie może nastąpić podczas kompilacji. Tak ++i/ i++nie nastąpi. Również sizeof(foo())nie wykona funkcji, ale zwróci poprawny typ.

rakesh
źródło
2
Wykonanie nie może nastąpić podczas kompilacji. ” Co masz na myśli?
ciekawy,
1
Kompilacja utworzy tylko kod obiektowy ... Kod obiektowy zostanie wykonany tylko wtedy, gdy użytkownik uruchomi plik binarny. Ponieważ sizeof dzieje się w czasie kompilacji, przy założeniu, że przyrost i ++ będzie niepoprawny.
rakesh
Skoro rozmiar sizeof dzieje się w czasie kompilacji ” masz na myśli: „jak sizeofwyrażenie wyrażające stałą czasową kompilacji”?
ciekawy,
Podobnie jak „#define” dzieje się podczas wstępnego przetwarzania, podobnie sizeof nastąpi w czasie kompilacji. Podczas kompilacji dostępne są wszystkie informacje o typie, więc rozmiar jest oceniany wtedy i tam podczas kompilacji, a wartość jest zastępowana. Jak już wspomniano wcześniej przez @pmg „From C99 Standard”.
rakesh
1
sizeof wydarzy się w czasie kompilacji ” dla czegoś, co nie jest tablicą o zmiennej długości
ciekawy
0

sizeofdziała w czasie kompilacji, ale x++można go oceniać tylko w czasie wykonywania. Aby rozwiązać ten problem, standard C ++ nakazuje, aby operand sizeofnie był oceniany. Standard C mówi:

Jeśli typ argumentu [of sizeof] jest tablicą o zmiennej długości, argument jest oceniany; w przeciwnym razie operand nie jest analizowany, a wynik jest stałą całkowitą.

Edward Karak
źródło
W C ++ nie ma VLA.
LF,
-2

sizeof() operator podaje rozmiar tylko typu danych, nie ocenia elementów wewnętrznych.

Munna
źródło
To nieprawda, sizeof()operator działa rekurencyjnie i otrzyma rozmiar w bajtach wszystkich elementów kontenera, członków klasy lub struktury itp. Możesz to bardzo łatwo udowodnić, tworząc prostą klasę z kilkoma członkami i wzywając sizeof()to. (Jednak wszystko, co jest wskaźnikiem, którego rozmiar nie może zobaczyć - tylko rozmiar wskaźnika). Dzieje się tak podczas kompilacji, jak stwierdzili inni komentatorzy: wyrażenia wewnątrz sizeof()nie są oceniane.
Tyler Shellberg