W C operator sizeof zwraca 8 bajtów przy przejściu 2,5 m, ale 4 bajty przy przejściu 1,25 m * 2

Nie rozumiem, dlaczego operator sizeof daje następujące wyniki:

sizeof( 2500000000 ) // => 8 (8 bytes).

... zwraca 8, a gdy wykonuję następujące czynności:

sizeof( 1250000000 * 2 ) // => 4 (4 bytes).

... zwraca 4, a nie 8 (czego się spodziewałem). Czy ktoś może wyjaśnić jak sizeof określa rozmiar wyrażenia (lub typu danych) i dlaczego w moim konkretnym przypadku tak się dzieje?

Domyślam się, że operator sizeof jest operatorem czasu kompilacji.

Pytanie: czy jest bieg operator czasu, który może ocenić te wyrażenia i wyprodukować oczekiwany wynik (bez odlewania)?

 65
Author: Jacob Pollack, 2013-05-30

8 answers

2500000000 nie mieści się w int, więc kompilator poprawnie interpretuje go jako long (lub long long, lub typ, w którym pasuje). 1250000000 robi, i tak robi 2. Parametr do sizeof nie jest oceniana, więc kompilator nie może wiedzieć, że mnożenie nie pasuje do int, a więc zwraca Rozmiar int.

Również, nawet jeśli parametr został oceniony, prawdopodobnie pojawi się przepełnienie (i nieokreślone zachowanie), ale prawdopodobnie nadal skutkuje 4.

Tutaj:

#include <iostream>
int main()
{
    long long x = 1250000000 * 2;
    std::cout << x;
}

Możesz odgadnąć wyjście? Jeśli myślisz, że to 2500000000, to się mylisz. Typ wyrażenia 1250000000 * 2 to int, ponieważ operandami są int i int, a mnożenie nie jest automatycznie promowane do większego typu danych, jeśli nie pasuje.

Http://ideone.com/4Adf97

Więc tutaj, gcc mówi, że to -1794967296, ale to nieokreślone zachowanie, więc może to być dowolna liczba. Liczba ta pasuje do int.

W dodawanie, jeśli rzucisz jeden z operandów do oczekiwanego typu (podobnie jak rzucasz liczby całkowite podczas dzielenia, jeśli szukasz wyniku nie-integer), zobaczysz, że to działa:

#include <iostream>
int main()
{
    long long x = (long long)1250000000 * 2;
    std::cout << x;
}

Daje poprawne 2500000000.

 123
Author: Luchian Grigore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-30 06:37:32

[Edit: na początku nie zauważyłem, że jest to napisane zarówno w C, jak i C++. Odpowiadam tylko w odniesieniu do C.]

Odpowiadając na twoje kolejne pytanie: "czy istnieje możliwość określenia ilości pamięci przydzielonej do wyrażenia lub zmiennej w czasie wykonywania?": no, niezupełnie. Problem w tym, że nie jest to zbyt dobrze uformowane pytanie.

"wyrażenia", w języku C (w przeciwieństwie do jakiejś konkretnej implementacji), w rzeczywistości nie używają żadnej pamięci . (Specyficzne implementacje potrzebują trochę kodu i / lub pamięci danych do przechowywania obliczeń, w zależności od tego, ile wyników zmieści się w rejestrach procesora itd.) Jeśli wynik wyrażenia nie jest schowany w zmiennej, po prostu znika (a kompilator często może pominąć kod czasu wykonania, aby obliczyć nigdy nie zapisany wynik). Język nie daje sposobu, aby zapytać o coś, czego nie zakłada, że istnieje, np. przestrzeń do przechowywania wyrażeń.

Zmienne natomiast zajmują miejsce przechowywania (pamięć). Deklaracja zmiennej mówi kompilatorowi, ile miejsca ma odłożyć. Z wyjątkiem tablic o zmiennej długości C99, Wymagana pamięć jest określana wyłącznie w czasie kompilacji , a nie w czasie wykonywania. Dlatego sizeof x jest ogólnie wyrażeniem stałym: kompilator może (a w rzeczywistości musi) określać wartość sizeof x w czasie kompilacji.

VLAs C99 są szczególnym wyjątkiem od reguły:

void f(int n) {
    char buf[n];
    ...
}

Przechowywania wymaganego dla buf nie jest (ogólnie) coś, co kompilator może znaleźć w czasie kompilacji, więc sizeof buf nie jest stałą czasu kompilacji. W tym przypadku buf jest faktycznie przydzielany w czasie wykonywania, a jego rozmiar jest określany tylko wtedy. Więc sizeof buf jest wyrażeniem obliczanym w trybie runtime.

W większości przypadków wszystko jest wielkości z góry, w czasie kompilacji, a jeśli wyrażenie przepełnia się w czasie wykonywania, zachowanie jest niezdefiniowane, zdefiniowane w implementacji lub dobrze zdefiniowane w zależności od typu. Podpisane całkowite przepełnienie, jak w 2,5 mld pomnożony przez 2, Gdy INT_MAX jest nieco ponad 2,7 miliarda, skutkuje "niezdefiniowanym zachowaniem". Unsigned integers robią arytmetykę modularną i tym samym pozwalają obliczyć w GF (2k ).

Jeśli chcesz mieć pewność, że niektóre obliczenia nie przepełnią się, musisz to obliczyć samodzielnie, w czasie wykonywania. Jest to duża część tego, co sprawia, że biblioteki multiprecision (takie jak gmp) są trudne do napisania w języku C-zwykle jest dużo łatwiejsze, a także szybsze, aby kodować duże części tego w assembly i skorzystaj ze znanych właściwości procesora (takich jak flagi przepełnienia lub pary podwójnej szerokości result-register -).

 8
Author: torek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-30 06:15:42

Luchian już na to odpowiedział. Tylko dla uzupełnienia..

Standard C11 stwierdza (standard C++ ma podobne linie), że typ liczby całkowitej bez przyrostka do oznaczenia typu jest derterminowany w następujący sposób:

Od 6.4.4 stałe (projekt C11):

Semantyka

4 wartość stałej dziesiętnej jest obliczana na podstawie 10; stała ósemkowa, baza 8; stała szesnastkowa, baza 16. Na leksykalnie pierwsza cyfra jest najbardziej znacząca.

5 Typ stałej całkowitej jest pierwszą z odpowiadających lista, w której można reprezentować jego wartość.

A tabela przedstawia się następująco:

Stała Dziesiętna

int
int long int 
long long int

Stała ósemkowa lub szesnastkowa

int
unsigned int
long int
unsigned long int
long long int
unsigned long long int

Dla Stałych ósemkowych i szesnastkowych możliwe są nawet typy niepodpisane. Więc w zależności od platformy, która z powyższej listy (int lub long int lub long long int) pasuje pierwszy (w kolejności) będzie typem liczby całkowitej literalnej.

 6
Author: P.P.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-30 11:03:20

Innym sposobem na odpowiedź jest stwierdzenie, że to, co jest istotne dla sizeof, nie jest wartością wyrażenia, ale jego typem. sizeof zwraca rozmiar pamięci dla typu, który może być podany jako typ lub jako wyrażenie. W tym przypadku kompilator obliczy ten typ w czasie kompilacji Bez faktycznego obliczenia wyrażenia(zgodnie ze znanymi regułami, na przykład jeśli wywołujesz funkcję, typem wynikowym jest typ zwracanej wartości).

Jak inne poster stwierdził, że istnieje wyjątek dla tablicy o zmiennej długości (której Rozmiar typu jest znany tylko w czasie wykonywania).

W innym słowie zwykle piszesz rzeczy takie jak sizeof(type) lub sizeof expression, gdzie wyrażenie jest wartością L. Wyrażenie prawie nigdy nie jest skomplikowanym obliczaniem (jak głupi przykład wywołania funkcji powyżej): i tak byłoby bezużyteczne, ponieważ nie jest oceniane.

#include <stdio.h>

int main(){
    struct Stype {
            int a;
    } svar;
    printf("size=%d\n", sizeof(struct Stype));
    printf("size=%d\n", sizeof svar);
    printf("size=%d\n", sizeof svar.a);
    printf("size=%d\n", sizeof(int));

}

Zauważ również, że sizeof jest słowem kluczowym języka, a nie nawiasy funkcji nie są konieczne przed końcowe wyrażenie (mamy taką samą regułę dla słowa kluczowego return).

 2
Author: kriss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-30 09:21:14

Dla Twojego kolejnego pytania, nie ma "operatora" i nie ma różnicy między rozmiarem" czasu kompilacji "wyrażenia, a rozmiarem "czasu wykonania".

Jeśli chcesz wiedzieć, czy dany typ może utrzymać wynik, którego szukasz, zawsze możesz spróbować czegoś takiego:

#include <stdio.h>
#include <limits.h>

int main(void) {
    int a = 1250000000;
    int b = 2;

    if ( (INT_MAX / (double) b) > a ) {
        printf("int is big enough for %d * %d\n", a, b);
    } else {
        printf("int is not big enough for %d * %d\n", a, b);
    }

    if ( (LONG_MAX / (double) b) > a ) {
        printf("long is big enough for %d * %d\n", a, b);
    } else {
        printf("long is not big enough for %d * %d\n", a, b);
    }

    return 0;
}

I (nieco) bardziej ogólne rozwiązanie, tylko dla skowronków:

#include <stdlib.h>
#include <stdio.h>
#include <limits.h>

/* 'gssim' is 'get size of signed integral multiplication */

size_t gssim(long long a, long long b);
int same_sign(long long a, long long b);

int main(void) {
    printf("size required for 127 * 1 is %zu\n", gssim(127, 1));
    printf("size required for 128 * 1 is %zu\n", gssim(128, 1));
    printf("size required for 129 * 1 is %zu\n", gssim(129, 1));
    printf("size required for 127 * -1 is %zu\n", gssim(127, -1));
    printf("size required for 128 * -1 is %zu\n", gssim(128, -1));
    printf("size required for 129 * -1 is %zu\n", gssim(129, -1));
    printf("size required for 32766 * 1 is %zu\n", gssim(32766, 1));
    printf("size required for 32767 * 1 is %zu\n", gssim(32767, 1));
    printf("size required for 32768 * 1 is %zu\n", gssim(32768, 1));
    printf("size required for -32767 * 1 is %zu\n", gssim(-32767, 1));
    printf("size required for -32768 * 1 is %zu\n", gssim(-32768, 1));
    printf("size required for -32769 * 1 is %zu\n", gssim(-32769, 1));
    printf("size required for 1000000000 * 2 is %zu\n", gssim(1000000000, 2));
    printf("size required for 1250000000 * 2 is %zu\n", gssim(1250000000, 2));

    return 0;
}

size_t gssim(long long a, long long b) {
    size_t ret_size;
    if ( same_sign(a, b) ) {
        if ( (CHAR_MAX / (long double) b) >= a ) {
            ret_size = 1;
        } else if ( (SHRT_MAX / (long double) b) >= a ) {
            ret_size = sizeof(short);
        } else if ( (INT_MAX / (long double) b) >= a ) {
            ret_size = sizeof(int);
        } else if ( (LONG_MAX / (long double) b) >= a ) {
            ret_size = sizeof(long);
        } else if ( (LLONG_MAX / (long double) b) >= a ) {
            ret_size = sizeof(long long);
        } else {
            ret_size = 0;
        }
    } else {
        if ( (SCHAR_MIN / (long double) llabs(b)) <= -llabs(a) ) {
            ret_size = 1;
        } else if ( (SHRT_MIN / (long double) llabs(b)) <= -llabs(a) ) {
            ret_size = sizeof(short);
        } else if ( (INT_MIN / (long double) llabs(b)) <= -llabs(a) ) {
            ret_size = sizeof(int);
        } else if ( (LONG_MIN / (long double) llabs(b)) <= -llabs(a) ) {
            ret_size = sizeof(long);
        } else if ( (LLONG_MIN / (long double) llabs(b)) <= -llabs(a) ) {
            ret_size = sizeof(long long);
        } else {
            ret_size = 0;
        }
    }
    return ret_size;
}

int same_sign(long long a, long long b) {
    if ( (a >= 0 && b >= 0) || (a <= 0 && b <= 0) ) {
        return 1;
    } else {
        return 0;
    }
}

Który w moim systemie wychodzi:

size required for 127 * 1 is 1
size required for 128 * 1 is 2
size required for 129 * 1 is 2
size required for 127 * -1 is 1
size required for 128 * -1 is 1
size required for 129 * -1 is 2
size required for 32766 * 1 is 2
size required for 32767 * 1 is 2
size required for 32768 * 1 is 4
size required for -32767 * 1 is 2
size required for -32768 * 1 is 2
size required for -32769 * 1 is 4
size required for 1000000000 * 2 is 4
size required for 1250000000 * 2 is 8
 2
Author: Paul Griffiths,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-14 21:12:16

Tak, sizeof() nie oblicza pamięci wymaganej do wyniku tego mnożenia.

W drugim przypadku oba literały: 1250000000 i 2 każdy wymaga 4 bytes pamięci, stąd sizeof () zwraca 4. Jeśli jedna z wartości była powyżej 4294967295 (2^32 - 1), otrzymałbyś 8.

Ale Nie wiem jak sizeof () zwrócił 8 dla 2500000000. Zwraca 4 na moim kompilatorze VS2012

 0
Author: Aravind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-30 04:50:55

Projekt C11 jest tutaj: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf Możesz znaleźć projekt Cx0 tutaj: http://c0x.coding-guidelines.com/6.5.3.4.html

W obu przypadkach, sekcja 6.5.3.4 jest tym, czego szukasz. Zasadniczo twój problem sprowadza się do tego:

// Example 1:
long long x = 2500000000;
int size = sizeof(x); // returns 8

// Example 2:
int x = 1250000000;
int y = 2;
int size = sizeof(x * y); // returns 4

W przykładzie 1, Masz long long (8 bajtów), więc zwraca 8. W przykładzie 2, masz int * int, który zwraca int, który jest 4 bajtów (więc zwraca 4).

To odpowiedz na pytanie o nagrodę: tak i nie. sizeof nie obliczy rozmiaru potrzebnego do operacji, którą próbujesz wykonać, ale powie Ci Rozmiar wyników, jeśli wykonasz operację z odpowiednimi etykietami:

long long x = 1250000000;
int y = 2;
int size = sizeof(x * y); // returns 8

// Alternatively
int size = sizeof(1250000000LL * 2); // returns 8

Musisz mu powiedzieć, że masz do czynienia z dużą liczbą, albo założy, że ma do czynienia z najmniejszym typem, jaki może (który w tym przypadku jest int).

 0
Author: Zac Howland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-14 19:49:05

Najprostsza odpowiedź w jednym wierszu to:

Sizeof () jest funkcją ocenianą podczas kompilacji, której dane wejściowe są Typu c, której wartość jest całkowicie ignorowana

WIĘCEJ SZCZEGÓŁÓW: ..w związku z tym, gdy 2500000000 zostanie skompilowane, musi być przechowywane tak długo, jak jest zbyt długie, aby zmieścić się w int, dlatego argument ten jest po prostu kompilowany jako "(type) long". Jednak 1250000000 i 2 oba pasują do typu "int", więc jest to typ przekazany do sizeof, ponieważ w wyniku wartość Nigdy nie jest przechowywana, ponieważ kompilator jest po prostu zainteresowany typem, mnożenie nigdy nie jest oceniane.

 0
Author: sbail95,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-26 18:36:21