¿Por qué #define TRUE (1 == 1) en una macro booleana C en lugar de simplemente como 1?

160

He visto definiciones en C

#define TRUE (1==1)
#define FALSE (!TRUE)

¿Es esto necesario? ¿Cuál es el beneficio sobre simplemente definir VERDADERO como 1 y FALSO como 0?

Robert Harvey
fuente
35
Y más: #define TRUE (’/’/’/’); #define FALSE (’-’-’-’)(tomado de coding-guidelines.com/cbook/cbook1_1.pdf página 871)
osgx
2
No, es paranoia por el despistado ^ Wunderinformed, realmente. En C, 1 y 0 hacen lo mismo en todas las circunstancias.
Jens
@osgx ¿Qué significa?
mrgloom

Respuestas:

155

Este enfoque utilizará el booleantipo real (y resolverá truey false) si el compilador lo admite. (específicamente, C ++)

Sin embargo, sería mejor verificar si C ++ está en uso (a través de la __cplusplusmacro) y realmente usar truey false.

En un compilador de C, esto es equivalente a 0y 1.
(tenga en cuenta que eliminar los paréntesis lo romperá debido al orden de las operaciones)

SLaks
fuente
77
Eso es incorrecto, los bools no se usan aquí. El resultado de 1==1es un int. (ver stackoverflow.com/questions/7687403/… .)
Mat
44
@ Mat: ¿Incluso en C ++, con el booleantipo?
SLaks
9
La pregunta está etiquetada con C, pero de hecho en C ++ los operadores relacionales devuelven trueo false.
Mat
55
@ Mat: supongo que dicho código está escrito en encabezados C para jugar bien con C ++
SLaks
20
@SLaks Si quisiera jugar bien con C ++, lo haría #define TRUE truey #define FALSE falsesiempre que __cplusplusesté definido.
Nikos C.
137

La respuesta es la portabilidad. Los valores numéricos de TRUEy FALSEno son importantes. Lo que es importante es que una afirmación como if (1 < 2)se evalúa en if (TRUE)y una declaración como if (1 > 2)evalúa a if (FALSE).

Por supuesto, en C, (1 < 2)evalúa 1y (1 > 2)evalúa a 0, por lo que, como han dicho otros, no hay una diferencia práctica en lo que respecta al compilador. Pero al permitir que el compilador defina TRUEy de FALSEacuerdo con sus propias reglas, está haciendo que sus significados sean explícitos para los programadores, y está garantizando la coherencia dentro de su programa y cualquier otra biblioteca (suponiendo que la otra biblioteca siga los estándares C ... estar asombrado).


Alguna historia
Algunos BASICs definidos FALSEcomo 0y TRUEcomo -1. Al igual que muchos lenguajes modernos, interpretaron cualquier valor distinto de cero como TRUE, pero evaluaron expresiones booleanas que eran verdaderas como -1. Su NOToperación se implementó agregando 1 y volteando el signo, porque era eficiente hacerlo de esa manera. Entonces se convirtió en 'NO x' -(x+1). Un efecto secundario de esto es que un valor como 5evalúa a TRUE, pero NOT 5evalúa a -6, ¡que también lo es TRUE! Encontrar este tipo de error no es divertido.

Mejores prácticas
Dadas las de facto reglas que cero se interpreta como FALSEy cualquier valor distinto de cero se interpreta como TRUE, usted debe nunca se comparen las expresiones booleanas de aspecto a TRUEoFALSE . Ejemplos:

if (thisValue == FALSE)  // Don't do this!
if (thatValue == TRUE)   // Or this!
if (otherValue != TRUE)  // Whatever you do, don't do this!

¿Por qué? Porque muchos programadores usan el atajo de tratar ints como bools. No son lo mismo, pero los compiladores generalmente lo permiten. Entonces, por ejemplo, es perfectamente legal escribir

if (strcmp(yourString, myString) == TRUE)  // Wrong!!!

Eso parece legítimo, y el compilador lo aceptará felizmente, pero probablemente no haga lo que quieras. Eso es porque el valor de retorno de strcmp()es

      0 si yourString == myString
    <0 si yourString < myString
    > 0 siyourString > myString

Entonces la línea de arriba TRUEsolo regresa cuando yourString > myString.

La forma correcta de hacer esto es

// Valid, but still treats int as bool.
if (strcmp(yourString, myString))

o

// Better: lingustically clear, compiler will optimize.
if (strcmp(yourString, myString) != 0)

Similar:

if (someBoolValue == FALSE)     // Redundant.
if (!someBoolValue)             // Better.
return (x > 0) ? TRUE : FALSE;  // You're fired.
return (x > 0);                 // Simpler, clearer, correct.
if (ptr == NULL)                // Perfect: compares pointers.
if (!ptr)                       // Sleazy, but short and valid.
if (ptr == FALSE)               // Whatisthisidonteven.

A menudo encontrará algunos de estos "malos ejemplos" en el código de producción, y muchos programadores experimentados juran por ellos: funcionan, algunos son más cortos que sus alternativas correctas (¿pedante?) Y los modismos son casi universalmente reconocidos. Pero considere: las versiones "correctas" no son menos eficientes, están garantizadas para ser portátiles, pasarán incluso las cartas más estrictas e incluso los nuevos programadores las entenderán.

¿No vale eso?

Adam Liss
fuente
66
(1==1)No es más portátil que 1. Las propias reglas del compilador son las del lenguaje C, que es claro e inequívoco sobre la semántica de la igualdad y los operadores relacionales. Nunca he visto a un compilador interpretar mal estas cosas.
Keith Thompson
1
En realidad, strcmpse sabe que el valor devuelto por es menor, igual o mayor que 0. No se garantiza que sea -1, 0 o 1 y hay plataformas en la naturaleza que no devuelven esos valores para ganar velocidad de implementación. Entonces, si es strcmp(a, b) == TRUEasí, a > bpero la implicación inversa podría no ser válida.
Maciej Piechotka
2
@KeithThompson - Quizás "portabilidad" era el término equivocado. Pero el hecho es que (1 == 1) es un valor booleano; 1 no lo es.
Adam Liss
2
@AdamLiss: en C, (1==1)y 1ambas son expresiones constantes de tipo intcon el valor 1. Son semánticamente idénticas. Supongo que puedes escribir código que se dirija a lectores que no lo saben, pero ¿dónde termina?
Keith Thompson
2
'no' 5 es, de hecho, -6, a nivel de bit.
woliveirajr
51

El (1 == 1)truco es útil para definir TRUEde una manera que sea transparente para C, pero que proporcione una mejor escritura en C ++. El mismo código puede interpretarse como C o C ++ si está escribiendo en un dialecto llamado "Clean C" (que se compila como C o C ++) o si está escribiendo archivos de encabezado de API que pueden ser utilizados por programadores de C o C ++.

En las unidades de traducción C, 1 == 1tiene exactamente el mismo significado que 1; y 1 == 0tiene el mismo significado que 0. Sin embargo, en las unidades de traducción de C ++, 1 == 1tiene tipo bool. Entonces la TRUEmacro definida de esa manera se integra mejor en C ++.

Un ejemplo de cómo se integra mejor es que, por ejemplo, si la función footiene sobrecargas por inty para bool, entonces foo(TRUE)elegirá la boolsobrecarga. Si TRUEsolo se define como 1, entonces no funcionará bien en C ++. foo(TRUE)querrá la intsobrecarga.

Por supuesto, C99 introdujo bool, truey falsey estos se pueden utilizar en los archivos de cabecera que trabajan con C99 y con C.

Sin embargo:

  • esta práctica de definir TRUEy FALSEcomo (0==0)y (1==0)predates C99.
  • Todavía hay buenas razones para mantenerse alejado de C99 y trabajar con C90.

Si está trabajando en un proyecto de C mixta y C ++, y no desea C99, definir el menor de los casos true, falsey boolen su lugar.

#ifndef __cplusplus
typedef int bool;
#define true (0==0)
#define false (!true)
#endif

Dicho esto, el 0==0truco fue (¿es?) Utilizado por algunos programadores incluso en código que nunca tuvo la intención de interactuar con C ++ de ninguna manera. Eso no compra nada y sugiere que el programador tiene un malentendido sobre cómo funcionan los booleanos en C.


En caso de que la explicación de C ++ no fuera clara, aquí hay un programa de prueba:

#include <cstdio>

void foo(bool x)
{
   std::puts("bool");  
}

void foo(int x)
{
   std::puts("int");  
}

int main()
{
   foo(1 == 1);
   foo(1);
   return 0;
}

La salida:

bool
int

En cuanto a la pregunta de los comentarios sobre cómo se sobrecargan las funciones de C ++ relevantes para la programación mixta de C y C ++. Estos solo ilustran una diferencia de tipo. Una razón válida para querer una trueconstante boolcuando se compila como C ++ es para un diagnóstico limpio. En sus niveles de advertencia más altos, un compilador de C ++ podría advertirnos sobre una conversión si pasamos un entero como boolparámetro. Una razón para escribir en Clean C no es solo que nuestro código es más portátil (ya que los compiladores de C ++ lo entienden, no solo los compiladores de C), sino que podemos beneficiarnos de las opiniones de diagnóstico de los compiladores de C ++.

Kaz
fuente
3
Excelente y subestimada respuesta. No es del todo obvio que las dos definiciones de TRUEdiferirán en C ++.
user4815162342
44
¿Cómo son relevantes las funciones sobrecargadas para el código que se compila como C y C ++?
Keith Thompson
@KeithThompson No se trata solo de sobrecargar, sino de escribir correctamente en general, la sobrecarga es solo el ejemplo más práctico cuando entra en juego. Por supuesto, el código C ++ sin sobrecargas, plantillas y todas esas cosas "complicadas" eliminadas por "compatibilidad C" realmente no se preocupan mucho por los tipos, pero eso no significa que uno deba derrocar las limitaciones conceptuales de tipo en un lenguaje dado .
Christian Rau
1
@ChristianRau: ¿Qué quieres decir con "realmente no le importan mucho los tipos"? Los tipos son centrales para el lenguaje C; cada expresión, valor y objeto en un programa en C tiene un tipo bien definido. Si desea definir algo diferente en C y en C ++ (en los raros casos en los que realmente necesita escribir código que se compila como C y C ++), puede usarlo #ifdef __cpluspluspara expresar su intención con mucha más claridad.
Keith Thompson
@KeithThompson Sí, sé lo importante que son los tipos. Es solo que sin todas las cosas conscientes de los tipos, como la sobrecarga y las plantillas, cosas como la diferenciación entre booly intno importan mucho en la práctica, ya que son implícitamente convertibles entre sí (y en C en realidad "lo mismo" , tenga en cuenta las citas , sin embargo) y no hay muchas situaciones en las que realmente necesite desambiguar entre los dos. "no mucho" probablemente era demasiado pesado, "mucho menos en comparación con el código que usa plantillas y sobrecarga" quizás hubiera sido mejor.
Christian Rau
18
#define TRUE (1==1)
#define FALSE (!TRUE)

es equivalente a

#define TRUE  1
#define FALSE 0

C ª.

El resultado de los operadores relacionales es 0o 1. 1==1está garantizado para ser evaluado 1y !(1==1)está garantizado para ser evaluado 0.

No hay absolutamente ninguna razón para usar la primera forma. Sin embargo, tenga en cuenta que la primera forma no es menos eficiente, ya que en casi todos los compiladores se evalúa una expresión constante en tiempo de compilación en lugar de en tiempo de ejecución. Esto está permitido de acuerdo con esta regla:

(C99, 6.6p2) "Se puede evaluar una expresión constante durante la traducción en lugar del tiempo de ejecución, y en consecuencia se puede usar en cualquier lugar que pueda ser una constante".

PC-Lint incluso emitirá un mensaje (506, valor booleano constante) si no utiliza un literal para TRUEy FALSEmacros:

Para C, TRUEdebe definirse como ser 1. Sin embargo, otros idiomas usan cantidades distintas de 1, por lo que algunos programadores sienten que !0está jugando de forma segura.

También en C99, las stdbool.hdefiniciones de macros booleanas truey false directamente usan literales:

#define true   1
#define false  0
ouah
fuente
1
Tengo una duda, TRUE se reemplaza en cada uso con 1 == 1, mientras que solo usando 1 reemplazará a 1, ¿no es el primer método de sobrecarga de comparación adicional ... o se hace un compilador optimizado?
pinkpanther
44
Las expresiones constantes de @pinkpanther generalmente se evalúan en tiempo de compilación y, por lo tanto, no inducen ninguna sobrecarga.
ouah
2
1==1está garantizado para ser evaluado a1
ouah
3
@NikosC. Esa es una buena pregunta. Esto es importante para el código de la forma if(foo == true), que pasará de ser simplemente una mala práctica a un buggy plano.
djechlin
1
+1 para señalar los peligros en (x == TRUE)puede tener un valor de verdad diferente que x.
Joshua Taylor
12

Aparte de C ++ (ya mencionado), otro beneficio es para las herramientas de análisis estático. El compilador eliminará cualquier ineficiencia, pero un analizador estático puede usar sus propios tipos abstractos para distinguir entre resultados de comparación y otros tipos enteros, por lo que sabe implícitamente que VERDADERO debe ser el resultado de una comparación y no debe suponerse que es compatible. con un entero

Obviamente, C dice que son compatibles, pero puede optar por prohibir el uso deliberado de esa función para ayudar a resaltar errores, por ejemplo, dónde alguien podría haber confundido &y &&/ o haber confundido su precedencia de operador.

sh1
fuente
1
Ese es un buen punto, y tal vez algunas de estas herramientas pueden incluso capturar código tonto a if (boolean_var == TRUE) través de una expansión a la if (boolean_var == (1 == 1))cual, gracias a la información de tipo mejorada del (1 == 1)nodo, se incluye en el patrón if (<*> == <boolean_expr>).
Kaz
4

La diferencia práctica es ninguna. 0se evalúa falsey 1se evalúa en true. El hecho de que use una expresión booleana ( 1 == 1) o 1, para definir true, no hace ninguna diferencia. Ambos son evaluados para int.

Tenga en cuenta que la biblioteca estándar de C proporciona una cabecera específica para definir booleanos: stdbool.h.

Zapato
fuente
por supuesto que no ... pero algunas personas podrían pensar lo contrario, especialmente para los números negativos por eso :)
pinkpanther
¿Qué? Lo tienes al revés. truese evalúa 1y falsese evalúa en 0. C no sabe acerca de los tipos booleanos nativos, son solo ints.
djechlin
En C, los operadores relacionales y de igualdad producen resultados de tipo int, con valor 0o 1. C tiene un tipo booleano real ( _Boolcon una macro booldefinida en <stdbool.h>, pero eso solo se agregó en C99, lo que no cambió la semántica de los operadores para usar el nuevo tipo.
Keith Thompson
@djechlin: A partir de la norma de 1999, C hace tener un tipo booleano nativa. Se llama _Booly <stdbool.h>tiene #define bool _Bool.
Keith Thompson
@KeithThompson, tienes razón acerca de la 1 == 1evaluación como int. Editado
Zapato
3

No sabemos el valor exacto al que TRUE es igual y los compiladores pueden tener sus propias definiciones. Entonces, lo que usted privilegia es usar el interno del compilador para la definición. Esto no siempre es necesario si tiene buenos hábitos de programación, pero puede evitar problemas con algún estilo de codificación incorrecto, por ejemplo:

if ((a> b) == TRUE)

Esto podría ser un desastre si define manualmente VERDADERO como 1, mientras que el valor interno de VERDADERO es otro.

capiggue
fuente
En C, el >operador siempre produce 1 para verdadero, 0 para falso. No hay posibilidad de que ningún compilador de C se equivoque. Las comparaciones de igualdad con TRUEy FALSEson de mal estilo; lo anterior se escribe más claramente como if (a > b). Pero la idea de que diferentes compiladores de C pueden tratar la verdad y lo falso de manera diferente es simplemente incorrecta.
Keith Thompson el
2
  1. Elemento de la lista

Por lo general, en el lenguaje de programación C, 1 se define como verdadero y 0 se define como falso. De ahí por qué ves lo siguiente con bastante frecuencia:

#define TRUE 1 
#define FALSE 0

Sin embargo, cualquier número que no sea igual a 0 también se evaluaría como verdadero en una declaración condicional. Por lo tanto, usando lo siguiente:

#define TRUE (1==1)
#define FALSE (!TRUE)

Puede demostrar explícitamente que está tratando de ir a lo seguro haciendo falso igual a lo que no es cierto.

Sabashan Ragavan
fuente
44
No lo llamaría "ir a lo seguro", sino que te estás dando una falsa sensación de seguridad.
dodgethesteamroller