En C, el operador sizeof devuelve 8 bytes cuando pasa 2,5 m pero 4 bytes cuando pasa 1,25 m * 2

15 minutos de lectura

avatar de usuario
jacob pollock

no entiendo porque la sizeof operador está produciendo los siguientes resultados:

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

… me devuelve 8, y cuando hago lo siguiente:

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

… devuelve 4, en lugar de 8 (que es lo que esperaba). ¿Alguien puede aclarar cómo sizeof determina el tamaño de una expresión (o tipo de datos) y por qué en mi caso específico ocurre esto?

Mi mejor conjetura es que el sizeof operator es un operador de tiempo de compilación.

Pregunta de recompensa: ¿Existe un operador de tiempo de ejecución que pueda evaluar estas expresiones y producir el resultado esperado (sin conversión)?

  • sizeof es para tipos, no literales, y se evalúa en tiempo de compilación. ¿Por qué necesita hacer esto?

    –Roger Rowland

    30 de mayo de 2013 a las 4:35


  • @RogerRowland a qué te refieres sizeof no es para literales?

    – Lucian Grigore

    30 de mayo de 2013 a las 4:37

  • sizeof se puede utilizar en un tipo o en una expresión. Los literales son expresiones; están bien.

    –Jonathan Leffler

    30 de mayo de 2013 a las 4:40

  • @RogerRowland en realidad es una práctica común y recomendada aplicar sizeof a objetos reales en lugar de clases.

    – Lucian Grigore

    30 de mayo de 2013 a las 4:40

  • @JacobPollack: a excepción de las matrices de longitud variable, el tamaño de cada expresión se determina en el momento de la compilación. Creo que lo que estás preguntando es, por ejemplo, dado int x = foo; int y = bar;qué tipo de entero (o qué tipo de entero) necesita para almacenar el resultado de multiplicar x por y sin desbordamiento. No hay una forma sencilla de hacerlo; C prácticamente te lo deja a ti.

    –Keith Thompson

    14/08/2013 a las 19:34

avatar de usuario
Luciano Grigore

2500000000 no cabe en un intpor lo que el compilador lo interpreta correctamente como un long (o long longo un tipo donde encaje). 1250000000 lo hace, y también lo hace 2. El parámetro a sizeof no se evalúapor lo que el compilador no puede saber que la multiplicación no cabe en un intpor lo que devuelve el tamaño de un int.

Además, incluso si se evaluó el parámetro, es probable que obtenga un desbordamiento (y un comportamiento indefinido), pero probablemente aún resulte en 4.

Aquí:

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

¿Puedes adivinar la salida? si crees que es 2500000000, estarías equivocado. El tipo de expresión 1250000000 * 2 es intporque los operandos son int y int y la multiplicación no se promueve automáticamente a un tipo de datos más grande si no encaja.

http://ideone.com/4Adf97

Así que aquí, gcc dice que es -1794967296, pero es un comportamiento indefinido, por lo que podría ser cualquier número. Este número encaja en un int.

Además, si convierte uno de los operandos al tipo esperado (al igual que convierte números enteros al dividir si está buscando un resultado no entero), verá que esto funciona:

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

da el correcto 2500000000.

  • Sí, para VC++, MSDN dice que no se evalúa una expresión – msdn.microsoft.com/en-us/library/4s7x1k91(v=vs.71).aspx

    –Roger Rowland

    30 de mayo de 2013 a las 4:40

  • @RogerRowland lo más importante, el estándar dice que el operando de sizeof no se evalúa.

    – Lucian Grigore

    30 de mayo de 2013 a las 4:41

  • En lugar de lanzar, mejor agrega un LL sufijo.

    – Oberón

    30 de mayo de 2013 a las 7:10

  • @Oberon ja ¡Veo lo que hiciste allí!

    – Lucian Grigore

    30 de mayo de 2013 a las 7:10

  • Estoy buscando a alguien que me cite el estándar C99. Sin embargo, esta sigue siendo una respuesta fantástica y, si nadie lo hace, la volveré a aceptar. Si también pudiera citar un estándar C99, le daré la recompensa y lo volveré a aceptar.

    –Jacob Pollack

    14 de agosto de 2013 a las 19:14

avatar de usuario
Torek

[Edit: I did not notice, initially, that this was posted as both C and C++. I’m answering only with respect to C.]

Respondiendo a su pregunta de seguimiento, “¿Hay alguna forma de determinar la cantidad de memoria asignada a una expresión o variable en tiempo de ejecución?”: bueno, no exactamente. El problema es que esta no es una pregunta muy bien formulada.

“Expresiones”, en C-el-lenguaje (a diferencia de alguna implementación específica), en realidad no usan ningún memoria. (Las implementaciones específicas necesitan algo de código y/o memoria de datos para contener los cálculos, dependiendo de cuántos resultados quepan en los registros de la CPU, etc.). Si el resultado de una expresión no se guarda en una variable, simplemente desaparece (y el compilador puede a menudo omite el código de tiempo de ejecución para calcular el resultado nunca guardado). El idioma no le brinda una forma de preguntar sobre algo que no asume que existe, es decir, espacio de almacenamiento para expresiones.

Las variables, por otro lado, ocupan almacenamiento (memoria). La declaración de una variable le dice al compilador cuánto almacenamiento reservar. Sin embargo, a excepción de los arreglos de longitud variable de C99, el almacenamiento requerido se determina puramente en compilar tiempo, no en tiempo de ejecución. Esta es la razón por sizeof x es generalmente una expresión constante: el compilador puede (y de hecho debe) determinar el valor de sizeof x en tiempo de compilación.

Los VLA de C99 son una excepción especial a la regla:

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

El almacenamiento necesario para buf no es (en general) algo que el compilador pueda encontrar en tiempo de compilación, por lo que sizeof buf no es una constante de tiempo de compilación. En este caso, buf en realidad se asigna en tiempo de ejecución y su tamaño solo se determina entonces. Asi que sizeof buf es una expresión calculada en tiempo de ejecución.

Sin embargo, en la mayoría de los casos, todo se dimensiona por adelantado, en tiempo de compilación, y si una expresión se desborda en tiempo de ejecución, el comportamiento es indefinido, definido por la implementación o bien definido según el tipo. Desbordamiento de enteros con signo, como en 2500 millones multiplicado por 2, cuando INT_MAX es solo un poco más de 2.7 mil millones, da como resultado un “comportamiento indefinido”. Los enteros sin signo hacen aritmética modular y, por lo tanto, le permiten calcular en GF(2k).

Si desea asegurarse de que algún cálculo no se desborde, eso es algo que debe calcular usted mismo, en tiempo de ejecución. Esto es una gran parte de lo que hace que las bibliotecas de multiprecisión (como gmp) sean difíciles de escribir en C; por lo general, es mucho más fácil y rápido codificar grandes partes de eso en ensamblador y aprovechar las propiedades conocidas de la CPU (como indicadores de desbordamiento o pares de registros de resultados de doble ancho).

  • Interesante, no sabía que C99 sizeof evalúa matrices en tiempo de ejecución. +1

    – Lucian Grigore

    30 de mayo de 2013 a las 6:42

  • no todas las matrices, no lo creo, solo aquellas en las que no puede determinar el tamaño en el momento de la compilación, donde tiene una longitud variable.

    – xaxxon

    30 de mayo de 2013 a las 7:56

  • @xaxxon: correcto, solo los VLA generan trabajo en tiempo de ejecución para sizeof.

    – torek

    30 de mayo de 2013 a las 8:03

avatar de usuario
PÁGINAS

Lucian ya lo contestó. Solo por completarlo..

El estándar C11 establece (el estándar C++ tiene líneas similares) que el tipo de un literal entero sin sufijo para designar el tipo se determina de la siguiente manera:

De 6.4.4 Constantes (Borrador C11):

Semántica

4 El valor de una constante decimal se calcula en base 10; el de una constante octal, base 8; el de una constante hexadecimal, base 16. El primer dígito léxico es el más significativo.

5 El tipo de una constante entera es el primero de la lista correspondiente en la que se puede representar su valor.

Y la tabla es la siguiente:

constante decimal

int
int long int 
long long int

Constante octal o hexadecimal

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

Para las constantes octales y hexadecimales, incluso los tipos sin signo son posibles. Entonces, dependiendo de su plataforma, cualquiera que esté en la lista anterior (int o int largo o int largo largo) encaja primero (en el orden) será el tipo de literal entero.

  • Sin embargo, realmente me pregunto qué hizo el compilador, cuando vio una palabra clave que pensó que debía manejar inmediatamente y reemplazarla con un valor constante, pero luego vio una expresión dentro. Esperaría que, 1: no compilara 2: hiciera los cálculos para un subconjunto limitado de expresiones o 3: pasara a una implementación en tiempo de ejecución de sizeof (que aparentemente se usa para tamaños de matrices dinámicas donde el tamaño se pasa como un parámetro int foo[3*n+1]; tamaño de (foo); en cuyo caso se habría ejecutado la multiplicación.

    – xaxxon

    30 de mayo de 2013 a las 7:55

  • Excepto los VLA, el compilador tiene la información de tamaño para los tipos. Por lo tanto, puede determinar si un literal entero encajará en uno de los tipos disponibles. Como dijo torek en su respuesta, se requiere que el compilador evalúe el operando de sizeof en el caso de C99 VLA. (Por cierto, los VLA se hacen opcionales en C11). Pero la evaluación del tamaño en tiempo de compilación es más sencilla que eso.

    – PP

    30 de mayo de 2013 a las 9:08

  • No estoy seguro de cómo exactamente el compilador calcula el tipo. Por ejemplo, gcc emite una advertencia para sizeof(99999999999999999999): warning: integer constant is too large for long type. No sé exactamente cómo funciona el compilador, cómo determina el tamaño de los literales. Pero verifica: 1. Si el literal tiene algún sufijo que designe el tipo. Si es así, use ese tipo para encajar. Por ejemplo, sizeof(1L) será el sizeof(long) aunque sea literal 1 cabe en un int.

    – PP

    30 de mayo de 2013 a las 9:08


  • 2. Si no hay sufijo, siga el orden anterior comenzando desde int para encontrar el tamaño. 3. Si ningún literal es demasiado grande para adaptarse a cualquiera de los tipos, emita un diagnóstico.

    – PP

    30 de mayo de 2013 a las 9:11

  • Es bueno citar el estándar C11 (que en este caso es prácticamente idéntico al estándar C99), pero dado que muchos programadores de C usan GCC o un compilador que pretende ser compatible con GCC, se debe señalar que, de forma predeterminada, GCC no usa la lista de tipos que cita, sino la lista C90 extendida al final con tipos no estándar más grandes: blog.frama-c.com/index.php?post/2012/01/20/Constants-quiz

    – Pascal Cuoq

    14/08/2013 a las 19:29


Otra forma de dar la respuesta es decir que lo que es relevante para sizeof no es el valor de la expresión sino su tipo. sizeof devuelve el tamaño de la memoria para un tipo que se puede proporcionar explícitamente como tipo o como expresión. En este caso, el compilador calculará este tipo en tiempo de compilación sin que realmente calculando la expresión (siguiendo reglas conocidas, por ejemplo, si llama a una función, el tipo resultante es el tipo del valor devuelto).

Como indicó otro cartel, hay una excepción para la matriz de longitud variable (cuyo tamaño de tipo solo se conoce en tiempo de ejecución).

En otras palabras, normalmente escribes cosas como sizeof(type) o sizeof expression donde expresión es un valor L. La expresión casi nunca es una computación compleja (como el estúpido ejemplo de llamar a una función anterior): de todos modos sería inútil ya que no se evalúa.

#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));

}

También tenga en cuenta que como sizeof es una palabra clave de idioma, no es necesario que los paréntesis de una función antes de la expresión final (tenemos el mismo tipo de regla para la palabra clave de retorno).

avatar de usuario
hombre cuervo

Para su pregunta de seguimiento, no hay un “operador” y no hay diferencia entre el tamaño del “tiempo de compilación” de una expresión y el tamaño del “tiempo de ejecución”.

Si desea saber si un tipo determinado puede contener el resultado que está buscando, siempre puede probar algo como esto:

#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;
}

y una solución (ligeramente) más general, solo por bromas:

#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;
    }
}

que, en mi sistema, salidas:

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

avatar de usuario
aravind

Sí, sizeof() no calcula la memoria requerida para el resultado de esa multiplicación.

En el segundo caso ambos literales: 1250000000 y 2 cada uno requiere 4 bytes de memoria, por lo que sizeof() devuelve 4. Si uno de los valores estaba por encima 4294967295 (2^32 - 1)hubieras conseguido 8.

Pero no sé cómo devolvió sizeof() 8 por 2500000000. Vuelve 4 en mi compilador VS2012

El Borrador C11 está aquí: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
Puede encontrar el borrador de Cx0 aquí: http://c0x.codificación-guidelines.com/6.5.3.4.html

En ambos casos, la sección 6.5.3.4 es lo que está buscando. Básicamente, su problema se reduce a esto:

// 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

En el ejemplo 1, usted tiene un long long (8 bytes), por lo que devuelve 8. En el ejemplo 2, tiene un int * int que devuelve un intque es de 4 bytes (por lo que devuelve 4).

Para responder a su pregunta de recompensa: sí y no. sizeof no calculará el tamaño necesario para la operación que intenta realizar, pero le indicará el tamaño de los resultados si realiza la operación con las etiquetas adecuadas:

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

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

Tienes que decirle que estás tratando con un número grande o asumirá que está tratando con el tipo más pequeño que puede (que en este caso es int).

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad