Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

204
Views
Comparando enteros sin signo con literales negativos

Tengo este sencillo programa en C.

 #include <stdlib.h> #include <stdio.h> #include <stdbool.h> bool foo (unsigned int a) { return (a > -2L); } bool bar (unsigned long a) { return (a > -2L); } int main() { printf("foo returned = %d\n", foo(99)); printf("bar returned = %d\n", bar(99)); return 0; }

Salida cuando ejecuto esto -

 foo returned = 1 bar returned = 0

Recreado en Godbolt aquí

Mi pregunta es por qué foo(99) devuelve verdadero pero bar(99) devuelve falso.

Para mí tiene sentido que la bar devuelva falso. Para simplificar, digamos que los largos son de 8 bits, entonces (usando el complemento a dos para el valor con signo):

 99 == 0110 0011 -2 == unsigned 254 == 1111 1110

Entonces, claramente, la instrucción CMP verá que 1111 1110 es más grande y devolverá falso.

Pero no entiendo lo que sucede detrás de escena en la función foo . El ensamblado para foo parece codificarse para devolver siempre mov eax,0x1 . Hubiera esperado que foo hiciera algo similar a bar . ¿Que esta pasando aqui?

over 4 years ago · Santiago Trujillo
3 answers
Answer question

0

Esto se trata en las clases de C y se especifica en la documentación. Así es como usa los documentos para resolver esto.

En el estándar C de 2018 , puede buscar > o "expresiones relacionales" en el índice para ver que se analizan en las páginas 68-69. En la página 68, encontrará la cláusula 6.5.8, que cubre los operadores relacionales, incluido > . Al leerlo, el párrafo 3 dice:

Si ambos operandos tienen tipo aritmético, se realizan las conversiones aritméticas habituales.

Las "conversiones aritméticas habituales" se enumeran en el índice como se define en la página 39. La página 39 tiene la cláusula 6.3.1.8, "Conversiones aritméticas habituales". Esta cláusula explica que los operandos de tipos aritméticos se convierten en un tipo común y proporciona reglas que determinan el tipo común. Para dos tipos de enteros de distinto signo, como el unsigned long y el long int en bar ( a y -2L ), dice que, si el tipo sin signo tiene un rango mayor o igual que el rango del otro tipo, el tipo con signo El tipo se convierte al tipo sin firmar.

"Rango" no está en el índice, pero puede buscar en el documento para encontrar que se trata en la cláusula 6.3.1.1, donde le dice que el rango de long int es mayor que el rango de int , y cualquier tipo sin firmar tiene el mismo rango que el tipo correspondiente.

Ahora puede considerar a > -2L en la bar , donde a es unsigned long . Aquí tenemos un unsigned long comparado con un long . Tienen el mismo rango, por lo que -2L se convierte en unsigned long . La conversión de un entero con signo a sin signo se trata en la cláusula 6.3.1.3. Dice que el valor se convierte envolviéndolo en módulo ULONG_MAX +1, por lo que el −2 signed long con signo produce un entero grande. Luego, comparar a , que tiene el valor 99, con un entero grande con > produce falso, por lo que se devuelve cero.

Para foo , continuamos con las reglas para las conversiones aritméticas habituales. Cuando el tipo sin signo no tiene un rango mayor o igual que el rango del tipo con signo, pero el tipo con signo puede representar todos los valores del tipo del operando con tipo sin signo, el operando con el tipo sin signo se convierte en el operando del tipo firmado. En foo , a es un unsigned int y -2L es long int . Presumiblemente, en su implementación de C, long int es de 64 bits, por lo que puede representar todos los valores de un unsigned int de 32 bits. Entonces se aplica esta regla, y a se convierte en long int . Esto no cambia el valor. Entonces, el valor original de a , 99, se compara con −2 con > , y esto da como resultado verdadero, por lo que se devuelve uno.

over 4 years ago · Santiago Trujillo Report

0

En la primera función

 bool foo (unsigned int a) { return (a > -2L); }

los dos operandos de la expresión a > -2L tienen el tipo long (el primer operando se convierte al tipo long debido a las conversiones aritméticas habituales porque el rango del tipo long es mayor que el rango del tipo unsigned int y todos los valores del tipo unsigned int en el sistema utilizado se puede representar con el tipo long ). Y es evidente que el valor positivo 99L es mayor que el valor negativo -2L .

La primera función podría producir el resultado 0 siempre que sizeof( long ) sea igual a sizeof( unsigned int ) . En este caso, el tipo long no puede representar todos los valores (positivos) del tipo unsigned int . Como resultado, debido a las conversiones aritméticas habituales, ambos operandos se convertirán al tipo unsigned long .

Por ejemplo, al ejecutar la función foo usando MS VS 2019 , donde sizeof( long ) es igual a 4 como sizeof( unsigned int ) , obtendrá el resultado 0 .

Aquí hay un programa de demostración escrito en C ++ que muestra visualmente la razón por la cual el resultado de una llamada de la función foo usando MS VS 2019 puede ser igual a 0 .

 #include <iostream> #include <iomanip> #include <type_traits> int main() { unsigned int x = 0; long y = 0; std::cout << "sizeof( unsigned int ) = " << sizeof( unsigned int ) << '\n'; std::cout << "sizeof( long ) = " << sizeof(long) << '\n'; std::cout << "std::is_same_v<decltype( x + y ), unsigned long> is " << std::boolalpha << std::is_same_v<decltype( x + y ), unsigned long> << '\n'; }

La salida del programa es

 sizeof( unsigned int ) = 4 sizeof( long ) = 4 std::is_same_v<decltype( x + y ), unsigned long> is true

En general, el resultado de la primera función es la implementación definida.

En las segundas funciones

 bool bar (unsigned long a) { return (a > -2L); }

ambos operandos tienen el tipo unsigned long (nuevamente debido a las conversiones aritméticas habituales y los rangos de los tipos unsigned long y con signed long son iguales entre sí, por lo que un objeto del tipo signed long se convierte al tipo unsigned long ) y -2L interpretado como unsigned long es mayor que 99 .

over 4 years ago · Santiago Trujillo Report

0

La razón de esto tiene que ver con las reglas de conversión de enteros.

En el primer caso, compara un unsigned int con un long usando el operador > , y en el segundo caso compara un unsigned long con un long .

Estos operandos primero deben convertirse a un tipo común usando las conversiones aritméticas usuales . Estos se explican en detalle en la sección 6.3.1.8p1 del estándar C , con el siguiente extracto centrado en las conversiones de enteros:

Si ambos operandos tienen el mismo tipo, entonces no se necesita más conversión.

De lo contrario, si ambos operandos tienen tipos de enteros con signo o ambos tienen tipos de enteros sin signo, el operando con el tipo de menor rango de conversión de enteros se convierte al tipo de operando con mayor rango.

De lo contrario, si el operando que tiene un tipo entero sin signo tiene un rango mayor o igual al rango del tipo del otro operando, entonces el operando con tipo entero con signo se convierte al tipo del operando con tipo entero sin signo.

De lo contrario, si el tipo del operando con tipo entero con signo puede representar todos los valores del tipo del operando con tipo entero sin signo, entonces el operando con tipo entero sin signo se convierte al tipo del operando con tipo entero con signo.

De lo contrario, ambos operandos se convierten al tipo entero sin signo correspondiente al tipo del operando con tipo entero con signo.

En el caso de comparar un unsigned int con un long se aplica el segundo párrafo en negrita. long tiene un rango más alto y (suponiendo que long es de 64 bits e int de 32 bits) puede contener todos los valores que un unsigned int , por lo que el operando unsigned int a se convierte en un long . Dado que el valor en cuestión está en el rango de long , la sección 6.3.1.3p1 dicta cómo ocurre la conversión:

Cuando un valor con tipo entero se convierte a otro tipo entero que no sea _Bool , si el valor puede ser representado por el nuevo tipo, no cambia

Entonces el valor se conserva y nos quedamos con 99 > -2 , lo cual es cierto.

En el caso de comparar un unsigned long con un long , se aplica el primer párrafo en negrita. Ambos tipos son del mismo rango con signos diferentes, por lo que la constante long -2L se convierte en unsigned long . -2 está fuera del rango de un unsigned long , por lo que debe ocurrir una conversión de valor. Esta conversión se especifica en el apartado 6.3.1.3p2:

De lo contrario, si el nuevo tipo no tiene signo, el valor se convierte sumando o restando repetidamente uno más que el valor máximo que se puede representar en el nuevo tipo hasta que el valor esté en el rango del nuevo tipo.

Por lo tanto, el valor long -2 se convertirá en el valor unsigned long 2 64 -2, suponiendo que el unsigned long sea de 64 bits. Así que nos quedamos con 99 > 2 64 -2, lo cual es falso.

over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!