¿El estándar C ++ permite que un bool no inicializado bloquee un programa?

514
Remz 2019-01-10 15:39.

Sé que un "comportamiento indefinido" en C ++ puede permitir que el compilador haga lo que quiera. Sin embargo, tuve un bloqueo que me sorprendió, ya que asumí que el código era lo suficientemente seguro.

En este caso, el problema real ocurrió solo en una plataforma específica usando un compilador específico, y solo si la optimización estaba habilitada.

Probé varias cosas para reproducir el problema y simplificarlo al máximo. Aquí hay un extracto de una función llamada Serialize, que tomaría un parámetro bool y copiaría la cadena trueo falseen un búfer de destino existente.

¿Esta función estaría en una revisión de código, no habría forma de saber que, de hecho, podría fallar si el parámetro bool fuera un valor no inicializado?

// Zero-filled global buffer of 16 characters
char destBuffer[16];

void Serialize(bool boolValue) {
    // Determine which string to print based on boolValue
    const char* whichString = boolValue ? "true" : "false";

    // Compute the length of the string we selected
    const size_t len = strlen(whichString);

    // Copy string into destination buffer, which is zero-filled (thus already null-terminated)
    memcpy(destBuffer, whichString, len);
}

Si este código se ejecuta con optimizaciones clang 5.0.0 +, puede fallar.

El operador ternario esperado boolValue ? "true" : "false"parecía lo suficientemente seguro para mí, estaba asumiendo, "Cualquiera que sea el valor de basura boolValueno importa, ya que se evaluará como verdadero o falso de todos modos".

He configurado un ejemplo de Compiler Explorer que muestra el problema en el desmontaje, aquí el ejemplo completo. Nota: para reproducir el problema, la combinación que encontré que funcionó es usar Clang 5.0.0 con optimización -O2.

#include <iostream>
#include <cstring>

// Simple struct, with an empty constructor that doesn't initialize anything
struct FStruct {
    bool uninitializedBool;

   __attribute__ ((noinline))  // Note: the constructor must be declared noinline to trigger the problem
   FStruct() {};
};

char destBuffer[16];

// Small utility function that allocates and returns a string "true" or "false" depending on the value of the parameter
void Serialize(bool boolValue) {
    // Determine which string to print depending if 'boolValue' is evaluated as true or false
    const char* whichString = boolValue ? "true" : "false";

    // Compute the length of the string we selected
    size_t len = strlen(whichString);

    memcpy(destBuffer, whichString, len);
}

int main()
{
    // Locally construct an instance of our struct here on the stack. The bool member uninitializedBool is uninitialized.
    FStruct structInstance;

    // Output "true" or "false" to stdout
    Serialize(structInstance.uninitializedBool);
    return 0;
}

El problema surge debido al optimizador: fue lo suficientemente inteligente para deducir que las cadenas "verdadero" y "falso" solo difieren en longitud en 1. Por lo tanto, en lugar de calcular realmente la longitud, utiliza el valor del bool en sí, que debería técnicamente ser 0 o 1, y es así:

const size_t len = strlen(whichString); // original code
const size_t len = 5 - boolValue;       // clang clever optimization

Si bien esto es "inteligente", por así decirlo, mi pregunta es: ¿El estándar C ++ permite que un compilador asuma que un bool solo puede tener una representación numérica interna de '0' o '1' y usarlo de esa manera?

¿O es este un caso de implementación definida, en cuyo caso la implementación asumió que todos sus bools solo contendrán 0 o 1, y cualquier otro valor es un territorio de comportamiento indefinido?

5 answers

292
Peter Cordes 2019-01-10 23:42.

Sí, ISO C ++ permite (pero no requiere) implementaciones para hacer esta elección.

Pero también tenga en cuenta que ISO C ++ permite que un compilador emita código que se bloquee a propósito (por ejemplo, con una instrucción ilegal) si el programa encuentra UB, por ejemplo, como una forma de ayudarlo a encontrar errores. (O porque es una DeathStation 9000. Ser estrictamente conforme no es suficiente para que una implementación de C ++ sea útil para cualquier propósito real). Entonces, ISO C ++ permitiría a un compilador hacer un ensamblaje que fallara (por razones totalmente diferentes) incluso en un código similar que lee un archivo uint32_t. Aunque se requiere que sea un tipo de diseño fijo sin representaciones de trampas.

Es una pregunta interesante sobre cómo funcionan las implementaciones reales, pero recuerde que incluso si la respuesta fuera diferente, su código no sería seguro porque C ++ moderno no es una versión portátil del lenguaje ensamblador.


Está compilando para el ¿Dónde se documenta la ABI de System V x86-64? , que especifica que un boolargumento como función en un registro está representado por los patrones de bits false=0ytrue=1 en los 8 bits bajos del registro 1 . En memoria, booles un tipo de 1 byte que nuevamente debe tener un valor entero de 0 o 1.

(Una ABI es un conjunto de opciones de implementación que los compiladores de la misma plataforma acuerdan para que puedan crear código que llame a las funciones de los demás, incluidos los tamaños de tipo, las reglas de diseño de estructuras y las convenciones de llamada).

ISO C ++ no lo especifica, pero esta decisión ABI está muy extendida porque hace que la conversión bool-> int sea barata (solo extensión cero) . No conozco ninguna ABI que no permita que el compilador asuma 0 o 1 boolpara cualquier arquitectura (no solo x86). Permite optimizaciones como !myboolcon xor eax,1para cambiar el bit bajo: Cualquier código posible que pueda cambiar un bit / entero / bool entre 0 y 1 en una sola instrucción de CPU . O compilando a&&ba bit a bit AND para booltipos. Algunos compiladores realmente aprovechan los valores booleanos como 8 bits en los compiladores. ¿Son ineficaces las operaciones sobre ellos? .

En general, la regla como si permite que el compilador aproveche las cosas que son verdaderas en la plataforma de destino para la que se está compilando , porque el resultado final será un código ejecutable que implemente el mismo comportamiento visible externamente que la fuente C ++. (Con todas las restricciones que Undefined Behavior impone a lo que en realidad es "visible externamente": no con un depurador, sino desde otro hilo en un programa C ++ legal / bien formado).

El compilador es, sin duda permitió a sacar el máximo provecho de una garantía ABI en su código-gen, y hacer que el código que has encontrado lo que optimiza strlen(whichString)a
5U - boolValue.
(Por cierto, esta optimización es algo inteligente, pero tal vez miope frente a la ramificación e inserción memcpycomo almacenes de datos inmediatos 2 ).

O el compilador podría haber creado una tabla de punteros y haberla indexado con el valor entero de bool, nuevamente asumiendo que era un 0 o 1. ( ¿El estándar C ++ permite que un bool no inicializado bloquee un programa? ).


Su __attribute((noinline))constructor con la optimización habilitada provocó un sonido metálico con solo cargar un byte de la pila para usarlo como uninitializedBool. Hizo espacio para el objeto maincon push rax(que es más pequeño y por varias razones tan eficiente como sub rsp, 8), por lo que cualquier basura que haya en AL al ingresar maines el valor para el que se usó uninitializedBool. Es por eso que en realidad obtuviste valores que no eran justos 0.

5U - random garbagepuede ajustarse fácilmente a un valor sin firmar grande, lo que lleva a memcpy a entrar en la memoria sin asignar. El destino está en el almacenamiento estático, no en la pila, por lo que no está sobrescribiendo una dirección de retorno o algo así.


Otras implementaciones podrían hacer elecciones diferentes, por ejemplo false=0y true=any non-zero value. Entonces clang probablemente no generaría código que fallara para esta instancia específica de UB. (Pero aún estaría permitido si quisiera). No conozco ninguna implementación que elija otra cosa para lo que hace x86-64 bool, pero el estándar C ++ permite muchas cosas que nadie hace o incluso querría hacer en hardware que se parezca a las CPU actuales.

ISO C ++ deja sin especificar lo que encontrará cuando examine o modifique la representación del objeto de unbool . (por ejemplo, memcpyingresando boolen unsigned char, lo cual puede hacer porque char*puede usar un alias para cualquier cosa. Y unsigned charse garantiza que no tiene bits de relleno, por lo que el estándar C ++ le permite formalmente representaciones de objetos hexdump sin ningún UB. Conversión de punteros para copiar el objeto la representación es diferente de la asignación char foo = my_bool, por supuesto, por lo que la booleanización a 0 o 1 no sucedería y obtendría la representación del objeto sin formato).

Ha "ocultado" parcialmente el UB en esta ruta de ejecución del compilador connoinline . Sin embargo, incluso si no está en línea, las optimizaciones entre procedimientos aún podrían crear una versión de la función que dependa de la definición de otra función. (Primero, clang es hacer un ejecutable, no una biblioteca compartida de Unix donde puede ocurrir la interposición de símbolos. En segundo lugar, la definición dentro de la class{}definición para que todas las unidades de traducción deben tener la misma definición. Como con la inlinepalabra clave).

Entonces, un compilador podría emitir solo una reto ud2(instrucción ilegal) como la definición de main, porque la ruta de ejecución que comienza en la parte superior de maininevitablemente encuentra un comportamiento indefinido. (Que el compilador puede ver en tiempo de compilación si decide seguir la ruta a través del constructor no en línea).

Cualquier programa que se encuentre con UB está totalmente indefinido durante toda su existencia. Pero UB dentro de una función o if()rama que en realidad nunca se ejecuta no corrompe el resto del programa. En la práctica, eso significa que los compiladores pueden decidir emitir una instrucción ilegal, o a ret, o no emitir nada y caer en el siguiente bloque / función, para todo el bloque básico que se puede probar en tiempo de compilación para contener o conducir a UB.

CCG y Sonido metálico en la práctica hacen realidad a veces emiten ud2en la UB, en lugar de intentar siquiera para generar código para rutas de ejecución que no tienen sentido. O para casos como caer al final de una no voidfunción, gcc a veces omitirá una retinstrucción. Si pensaba que "mi función volverá con cualquier basura que haya en RAX", está muy equivocado. Los compiladores modernos de C ++ ya no tratan el lenguaje como un lenguaje ensamblador portátil. Su programa realmente tiene que ser válido en C ++, sin hacer suposiciones sobre cómo se vería una versión independiente no en línea de su función en asm.

Otro ejemplo divertido es ¿Por qué el acceso no alineado a la memoria mmap a veces se produce por defecto en AMD64?. x86 no falla en los enteros no alineados, ¿verdad? Entonces, ¿por qué un desalineado uint16_t*sería un problema? Porque alignof(uint16_t) == 2, y violar esa suposición llevó a un error de segmentación al auto-vectorizar con SSE2.

Consulte también Lo que todo programador de C debe saber sobre el comportamiento indefinido # 1/3 , un artículo de un desarrollador clang.

Punto clave: si el compilador notó el UB en tiempo de compilación, podría "romper" (emitir un conjunto sorprendente) la ruta a través de su código que causa UB incluso si apunta a un ABI donde cualquier patrón de bits es una representación de objeto válida bool.

Espere una hostilidad total hacia muchos errores por parte del programador, especialmente cosas sobre las que advierten los compiladores modernos. Es por eso que debe usar -Wally corregir las advertencias. C ++ no es un lenguaje fácil de usar, y algo en C ++ puede ser inseguro incluso si fuera seguro en el conjunto del destino para el que está compilando. (por ejemplo, el desbordamiento firmado es UB en C ++ y los compiladores asumirán que no sucede, incluso al compilar para el complemento x86 de 2, a menos que lo use clang/gcc -fwrapv).

La UB visible en tiempo de compilación siempre es peligrosa, y es realmente difícil estar seguro (con la optimización del tiempo de enlace) de que realmente ha ocultado la UB del compilador y, por lo tanto, puede razonar sobre qué tipo de asm generará.

No ser demasiado dramático; A menudo, los compiladores te permiten salirte con la tuya en algunas cosas y emitir código como esperabas incluso cuando algo es UB. Pero tal vez sea un problema en el futuro si los desarrolladores de compiladores implementan alguna optimización que obtenga más información sobre rangos de valores (por ejemplo, que una variable no sea negativa, tal vez permitiéndole optimizar la extensión de signo para liberar la extensión cero en x86- 64). Por ejemplo, en gcc y clang actuales, hacer tmp = a+INT_MINno se optimiza a<0como siempre falso, solo que tmpsiempre es negativo. (Porque INT_MIN+ a=INT_MAXes negativo en el objetivo de complemento a 2 y ano puede ser más alto que eso).

Por lo tanto, gcc / clang no retrocede actualmente para derivar información de rango para las entradas de un cálculo, solo en los resultados basados ​​en la suposición de que no hay desbordamiento firmado: ejemplo en Godbolt . No sé si esta optimización se "pierde" intencionalmente en nombre de la facilidad de uso o qué.

También tenga en cuenta que las implementaciones (también conocidas como compiladores) pueden definir el comportamiento que ISO C ++ deja sin definir . Por ejemplo, todos los compiladores que admiten los elementos intrínsecos de Intel (como _mm_add_ps(__m128, __m128)para la vectorización SIMD manual) deben permitir la formación de punteros mal alineados, que es UB en C ++ incluso si no los elimina. __m128i _mm_loadu_si128(const __m128i *)realiza cargas desalineadas tomando un __m128i*argumento desalineado , no un void*o char*. ¿Es la `reinterpretación_cast` entre el puntero vectorial de hardware SIMD y el tipo correspondiente un comportamiento indefinido?

GNU C / C ++ también define el comportamiento de desplazamiento a la izquierda de un número con signo negativo (incluso sin él -fwrapv), por separado de las reglas UB normales de desbordamiento con signo. ( ¿Por qué la operación de desplazamiento a la izquierda invoca un comportamiento indefinido cuando el operando del lado izquierdo tiene un valor negativo? , mientras que los desplazamientos a la derecha de los números con signo están definidos por la implementación (lógico frente a aritmético); las implementaciones de buena calidad eligen aritmética en HW que tiene desplazamientos aritméticos a la derecha, pero ISO C ++ no especifica). Esto está documentado en la sección Integer del manual de GCC , junto con la definición del comportamiento definido por la implementación que los estándares C requieren que las implementaciones definan de una forma u otra.

Definitivamente hay problemas de calidad de implementación que preocupan a los desarrolladores de compiladores; generalmente no están tratando de hacer compiladores que sean intencionalmente hostiles, pero aprovechar todos los baches de UB en C ++ (excepto los que eligen definir) para optimizar mejor puede ser casi indistinguible a veces.


Nota al pie 1 : Los 56 bits superiores pueden ser basura que el destinatario de la llamada debe ignorar, como es habitual en los tipos más estrechos que un registro.

( Otros ABIs hacen tomar decisiones diferentes aquí . Algunos no requieren tipos enteros estrechas ser cero o signo extendido para llenar un registro o cuando se pasa a regresar de funciones, como MIPS64 y PowerPC64. Ver la última sección de MOVZX falta registro de 32 bits a registro de 64 bits ).

Por ejemplo, una persona que llama podría haber calculado a & 0x01010101en RDI y haberlo usado para otra cosa antes de llamar bool_func(a&1). La persona que llama podría optimizar &1porque ya lo hizo con el byte bajo como parte de and edi, 0x01010101, y sabe que el destinatario de la llamada debe ignorar los bytes altos.

O si se pasa un bool como tercer argumento, tal vez un llamador que optimiza el tamaño del código lo carga con, en mov dl, [mem]lugar de movzx edx, [mem], guardar 1 byte a costa de una dependencia falsa del valor anterior de RDX (u otro efecto de registro parcial, dependiendo en el modelo de CPU). O para el primer argumento, en mov dil, byte [r10]lugar de movzx edi, byte [r10], porque ambos requieren un prefijo REX de todos modos.

Es por eso que clang emite movzx eax, dildentro Serialize, en lugar de sub eax, edi. (Para args enteros, clang viola esta regla ABI, en cambio, depende del comportamiento indocumentado de gcc y clang a cero o signo-extender enteros estrechos a 32 bits. ¿Se requiere un signo o una extensión cero al agregar un desplazamiento de 32 bits a un puntero para el ABI x86-64? Así que estaba interesado en ver que no hace lo mismo para bool.)


Nota al pie 2: después de la bifurcación, solo tendrá un movalmacén de 4 bytes inmediato o de 4 bytes + 1 byte. La longitud está implícita en los anchos de la tienda + compensaciones.

OTOH, glibc memcpy hará dos cargas / almacenes de 4 bytes con una superposición que depende de la longitud, por lo que esto realmente termina haciendo que todo esté libre de ramas condicionales en el booleano. Vea el L(between_4_7):bloque en memcpy / memmove de glibc. O al menos, siga el mismo camino para cualquier booleano en la ramificación de memcpy para seleccionar un tamaño de fragmento.

Si está en línea, puede usar 2x mov-immediate + cmovy un desplazamiento condicional, o puede dejar los datos de la cadena en la memoria.

O si está sintonizando Intel Ice Lake ( con la función Fast Short REP MOV ), un real rep movsbpodría ser óptimo. glibc memcpypodría comenzar a usarse rep movsb para tamaños pequeños en CPU con esa función, ahorrando muchas ramificaciones.


Herramientas para detectar UB y uso de valores no inicializados

En gcc y clang, puede compilar con -fsanitize=undefinedpara agregar instrumentación en tiempo de ejecución que advertirá o producirá un error en UB que ocurra en tiempo de ejecución. Sin embargo, eso no capturará variables unitarias. (Porque no aumenta los tamaños de letra para dejar espacio para un bit "no inicializado").

Ver https://developers.redhat.com/blog/2014/10/16/gcc-undefined-behavior-sanitizer-ubsan/

Para encontrar el uso de datos no inicializados, hay Address Sanitizer y Memory Sanitizer en clang / LLVM. https://github.com/google/sanitizers/wiki/MemorySanitizer muestra ejemplos de clang -fsanitize=memory -fPIE -piedetección de lecturas de memoria no inicializadas. Podría funcionar mejor si compila sin optimización, por lo que todas las lecturas de variables terminan cargándose desde la memoria en el asm. Muestran que se usa -O2en un caso en el que la carga no se optimizaría. Yo no lo he probado. (En algunos casos, por ejemplo, no inicializar un acumulador antes de sumar una matriz, clang -O3 emitirá un código que se suma en un registro vectorial que nunca se inicializó. Por lo tanto, con la optimización, puede tener un caso en el que no hay lectura de memoria asociada con la UB . Pero -fsanitize=memorycambia el asm generado y puede resultar en una verificación para esto.)

Tolerará la copia de memoria no inicializada y también operaciones lógicas y aritméticas simples con él. En general, MemorySanitizer rastrea silenciosamente la propagación de datos no inicializados en la memoria e informa de una advertencia cuando se toma (o no se toma) una rama de código en función de un valor no inicializado.

MemorySanitizer implementa un subconjunto de funciones que se encuentran en Valgrind (herramienta Memcheck).

Debería funcionar para este caso porque la llamada a glibc memcpycon un lengthcálculo de memoria no inicializada resultará (dentro de la biblioteca) en una rama basada en length. Si hubiera incorporado una versión completamente sin sucursales que solo usara cmov, indexación y dos tiendas, es posible que no hubiera funcionado.

Valgrind'smemcheck también buscará este tipo de problema, nuevamente sin quejarse si el programa simplemente copia datos no inicializados. Pero dice que detectará cuando un "salto o movimiento condicional depende de valores no inicializados", para tratar de detectar cualquier comportamiento visible externamente que dependa de datos no inicializados.

Quizás la idea detrás de no marcar solo una carga es que las estructuras pueden tener relleno, y copiar toda la estructura (incluido el relleno) con una carga / almacenamiento de vector amplio no es un error, incluso si los miembros individuales solo se escribieron uno a la vez. A nivel de conjunto, se ha perdido la información sobre lo que se estaba rellenando y lo que en realidad es parte del valor.

56
rici 2019-01-10 15:59.

El compilador puede asumir que un valor booleano pasado como argumento es un valor booleano válido (es decir, uno que ha sido inicializado o convertido a trueo false). El truevalor no tiene que ser el mismo que el entero 1 - de hecho, puede haber varias representaciones de truey false- pero el parámetro debe ser una representación válida de uno de esos dos valores, donde "representación válida" es implementación- definido.

Entonces, si no puede inicializar a bool, o si logra sobrescribirlo a través de algún puntero de un tipo diferente, las suposiciones del compilador serán incorrectas y se producirá un comportamiento indefinido. Le habían advertido:

50) El uso de un valor bool de las formas descritas por esta Norma Internacional como "indefinido", como al examinar el valor de un objeto automático no inicializado, puede hacer que se comporte como si no fuera verdadero ni falso. (Nota a pie de página del párrafo 6 de §6.9.1, Tipos fundamentales)

52
M.M 2019-01-10 16:12.

La función en sí es correcta, pero en su programa de prueba, la declaración que llama a la función provoca un comportamiento indefinido al usar el valor de una variable no inicializada.

El error está en la función de llamada y podría detectarse mediante la revisión del código o el análisis estático de la función de llamada. Usando el enlace del explorador de su compilador, el compilador gcc 8.2 detecta el error. (Tal vez podría presentar un informe de error contra clang que no encuentra el problema).

El comportamiento indefinido significa que puede suceder cualquier cosa , lo que incluye que el programa se bloquee unas pocas líneas después del evento que desencadenó el comportamiento indefinido.

NÓTESE BIEN. La respuesta a "¿Puede el comportamiento indefinido causar _____?" es siempre "Sí". Esa es literalmente la definición de comportamiento indefinido.

23
Barmar 2019-01-10 16:02.

Un bool solo puede contener los valores dependientes de la implementación usados ​​internamente para truey false, y el código generado puede asumir que solo contendrá uno de estos dos valores.

Normalmente, la implementación utilizará el entero 0for falsey 1for true, para simplificar las conversiones entre booly int, y if (boolvar)generar el mismo código que if (intvar). En ese caso, uno puede imaginar que el código generado para el ternario en la asignación usaría el valor como índice en una matriz de punteros a las dos cadenas, es decir, podría convertirse en algo como:

// the compile could make asm that "looks" like this, from your source
const static char *strings[] = {"false", "true"};
const char *whichString = strings[boolValue];

Si boolValueno está inicializado, en realidad podría contener cualquier valor entero, lo que provocaría el acceso fuera de los límites de la stringsmatriz.

15
Tom Tanner 2019-01-11 01:48.

Resumiendo mucho su pregunta, está preguntando: ¿El estándar C ++ permite que un compilador asuma boolque solo puede tener una representación numérica interna de '0' o '1' y usarlo de tal manera?

El estándar no dice nada sobre la representación interna de a bool. Solo define lo que sucede cuando se lanza un boola un int(o viceversa). Principalmente, debido a estas conversiones integrales (y al hecho de que la gente depende mucho de ellas), el compilador usará 0 y 1, pero no tiene que hacerlo (aunque tiene que respetar las restricciones de cualquier ABI de nivel inferior que use ).

Entonces, el compilador, cuando ve un, booltiene derecho a considerar que dicho boolcontiene cualquiera de los patrones de bits true"o" falsey hacer lo que le parezca. Así que si los valores de truey falseson 1 y 0, respectivamente, el compilador se permite de hecho a optimizar el strlena 5 - <boolean value>. ¡Son posibles otros comportamientos divertidos!

Como se indica repetidamente aquí, el comportamiento indefinido tiene resultados indefinidos. Incluyendo pero no limitado a

  • Tu código funciona como esperabas
  • Tu código falla en momentos aleatorios
  • Su código no se está ejecutando en absoluto.

Vea lo que todo programador debe saber sobre el comportamiento indefinido

MORE COOL STUFF

La estrella de HGTV, Christina Hall, revela que tiene 'envenenamiento por mercurio y plomo' probablemente por voltear 'casas asquerosas'

La estrella de HGTV, Christina Hall, revela que tiene 'envenenamiento por mercurio y plomo' probablemente por voltear 'casas asquerosas'

La estrella de HGTV, Christina Hall, revela que le diagnosticaron envenenamiento por mercurio y plomo, probablemente debido a su trabajo como manipuladora de casas.

La estrella de 'Love Is Blind' Brennon Lemieux responde a los cargos de violencia doméstica

La estrella de 'Love Is Blind' Brennon Lemieux responde a los cargos de violencia doméstica

Recientemente salió a la luz un informe policial que acusa a la estrella de 'Love Is Blind', Brennon, de violencia doméstica. Ahora, Brennon ha respondido a los reclamos.

Wynonna Judd se dio cuenta de que ahora es la matriarca de la familia Judd en un momento festivo de pánico

Wynonna Judd se dio cuenta de que ahora es la matriarca de la familia Judd en un momento festivo de pánico

Conozca cómo Wynonna Judd se dio cuenta de que ahora es la matriarca de la familia mientras organizaba la primera celebración de Acción de Gracias desde que murió su madre, Naomi Judd.

Experto en lenguaje corporal explica los 'paralelos' entre Kate Middleton y la princesa Diana

Experto en lenguaje corporal explica los 'paralelos' entre Kate Middleton y la princesa Diana

Descubra por qué un destacado experto en lenguaje corporal cree que es fácil trazar "tales paralelismos" entre la princesa Kate Middleton y la princesa Diana.

Los láseres arrojan luz sobre por qué necesita cerrar la tapa antes de descargar

Los láseres arrojan luz sobre por qué necesita cerrar la tapa antes de descargar

Los inodoros arrojan columnas de aerosol invisibles con cada descarga. ¿Como sabemos? La prueba fue capturada por láseres de alta potencia.

The Secrets of Airline Travel Quiz

The Secrets of Airline Travel Quiz

Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

¿Caduca el repelente de insectos?

¿Caduca el repelente de insectos?

¿Sigue siendo efectivo ese lote de repelente de insectos que te quedó del verano pasado? Si es así, ¿por cuánto tiempo?

Se revela la estatua de Godzilla más nueva de Tokio

Se revela la estatua de Godzilla más nueva de Tokio

Anteriormente, Kotaku informó que un hotel Godzilla se estaba abriendo en Tokio este abril. Junto al hotel, estaba programada la aparición de una enorme cabeza de 'Zilla, pero todo lo que hemos visto fueron imágenes conceptuales computarizadas.

El alcalde de Chicago realmente quiere que Elon Musk perfore un túnel debajo de la ciudad

El alcalde de Chicago realmente quiere que Elon Musk perfore un túnel debajo de la ciudad

Foto: Getty Desde que lanzó The Boring Company hace un año, Elon Musk ha mencionado varios sitios de construcción posibles para el negocio de perforación de túneles y ha descartado una vaga referencia a una aprobación gubernamental "verbal" para un túnel Hyperloop que conecta la ciudad de Nueva York y Washington. , CC. Pero ahora sabemos que al menos un alcalde quiere que Musk perfore un agujero debajo de su ciudad.

Ponle una tapa. En realidad, ponle una tapa a todo. Consigue 12 tapas de cocina elásticas de silicona por $14. [Exclusivo]

Ponle una tapa. En realidad, ponle una tapa a todo. Consigue 12 tapas de cocina elásticas de silicona por $14. [Exclusivo]

Tapas elásticas de silicona de Tomorrow's Kitchen, paquete de 12 | $14 | Amazonas | Código promocional 20OFFKINJALids son básicamente los calcetines de la cocina; siempre perdiéndose, dejando contenedores huérfanos que nunca podrán volver a cerrarse. Pero, ¿y si sus tapas pudieran estirarse y adaptarse a todos los recipientes, ollas, sartenes e incluso frutas en rodajas grandes que sobran? Nunca más tendrás que preocuparte por perder esa tapa tan específica.

Cuéntanos tus mejores trucos de Washington, DC

Cuéntanos tus mejores trucos de Washington, DC

Hemos pirateado algunas ciudades industriales en esta columna, como Los Ángeles y Las Vegas. Ahora es el momento de una ciudad militar-industrial-compleja.

Patinaje artístico de EE. UU. 'frustrado' por falta de decisión final en evento por equipos, pide una decisión justa

Patinaje artístico de EE. UU. 'frustrado' por falta de decisión final en evento por equipos, pide una decisión justa

El equipo está a la espera de las medallas que ganó en los Juegos Olímpicos de Invierno de 2022 en Beijing, ya que se está resolviendo un caso de dopaje que involucra a la patinadora artística rusa Kamila Valieva.

Los compradores de Amazon dicen que duermen 'como un bebé mimado' gracias a estas fundas de almohada de seda que cuestan tan solo $ 10

Los compradores de Amazon dicen que duermen 'como un bebé mimado' gracias a estas fundas de almohada de seda que cuestan tan solo $ 10

Miles de compradores de Amazon recomiendan la funda de almohada de seda Mulberry, y está a la venta en este momento. La funda de almohada de seda viene en varios colores y ayuda a mantener el cabello suave y la piel clara. Compre las fundas de almohada de seda mientras tienen hasta un 46 por ciento de descuento en Amazon

Se busca al corredor de los Bengals Joe Mixon por orden de arresto emitida por presuntamente apuntar con un arma de fuego a una mujer

Se busca al corredor de los Bengals Joe Mixon por orden de arresto emitida por presuntamente apuntar con un arma de fuego a una mujer

El jueves se presentó una denuncia de delito menor amenazante agravado contra Joe Mixon.

Profesor de la Universidad de Purdue arrestado por presuntamente traficar metanfetamina y proponer favores sexuales a mujeres

Profesor de la Universidad de Purdue arrestado por presuntamente traficar metanfetamina y proponer favores sexuales a mujeres

El Departamento de Policía de Lafayette comenzó a investigar a un profesor de la Universidad de Purdue en diciembre después de recibir varias denuncias de un "hombre sospechoso que se acercaba a una mujer".

Concept Drift: el mundo está cambiando demasiado rápido para la IA

Concept Drift: el mundo está cambiando demasiado rápido para la IA

Al igual que el mundo que nos rodea, el lenguaje siempre está cambiando. Mientras que en eras anteriores los cambios en el idioma ocurrían durante años o incluso décadas, ahora pueden ocurrir en cuestión de días o incluso horas.

India me está pateando el culo

India me está pateando el culo

Estoy de vuelta por primera vez en seis años. No puedo decirte cuánto tiempo he estado esperando esto.

ℝ

“And a river went out of Eden to water the garden, and from thence it was parted and became into four heads” Genesis 2:10. ? The heart is located in the middle of the thoracic cavity, pointing eastward.

¿Merrick Garland le ha fallado a Estados Unidos?

Es más de la mitad de la presidencia de Biden. ¿Qué está esperando Merrick Garland?

¿Merrick Garland le ha fallado a Estados Unidos?

Creo, un poco tarde en la vida, en dar oportunidades a la gente. Generosamente.

Language