¿Por qué f (i = -1, i = -1) es un comportamiento indefinido?

269
Nicu Stiurca 2014-02-10 20:31.

Estaba leyendo sobre las infracciones del orden de evaluación y dan un ejemplo que me desconcierta.

1) Si un efecto secundario en un objeto escalar no está secuenciado en relación con otro efecto secundario en el mismo objeto escalar, el comportamiento no está definido.

// snip
f(i = -1, i = -1); // undefined behavior

En este contexto, ies un objeto escalar , que aparentemente significa

Los tipos aritméticos (3.9.1), los tipos de enumeración, los tipos de puntero, los tipos de puntero a miembros (3.9.2), std :: nullptr_t y las versiones calificadas por cv de estos tipos (3.9.3) se denominan colectivamente tipos escalares.

No veo cómo la declaración es ambigua en ese caso. Me parece que independientemente de si el primer o segundo argumento se evalúa primero, itermina como -1, y ambos argumentos también lo son -1.

¿Alguien puede aclarar por favor?


ACTUALIZAR

Realmente aprecio toda la discusión. Hasta ahora, me gusta mucho la respuesta de @ harmic, ya que expone las trampas y complejidades de definir esta declaración a pesar de lo directa que parece a primera vista. @ acheong87 señala algunos problemas que surgen al usar referencias, pero creo que eso es ortogonal al aspecto de efectos secundarios no secuenciados de esta pregunta.


RESUMEN

Dado que esta pregunta recibió mucha atención, resumiré los puntos / respuestas principales. Primero, permítanme una pequeña digresión para señalar que "por qué" puede tener significados estrechamente relacionados pero sutilmente diferentes, a saber, "por qué causa ", "por qué razón " y "con qué propósito ". Agruparé las respuestas por cuál de esos significados de "por qué" abordaron.

por que causa

La respuesta principal aquí proviene de Paul Draper , con Martin J contribuyendo con una respuesta similar pero no tan extensa. La respuesta de Paul Draper se reduce a

Es un comportamiento indefinido porque no está definido cuál es el comportamiento.

En general, la respuesta es muy buena en términos de explicar lo que dice el estándar C ++. También aborda algunos casos relacionados de UB como f(++i, ++i);y f(i=1, i=-1);. En el primero de los casos relacionados, no está claro si el primer argumento debería ser i+1y el segundo i+2o viceversa; en el segundo, no está claro si idebería ser 1 o -1 después de la llamada a la función. Ambos casos son UB porque se rigen por la siguiente regla:

Si un efecto secundario en un objeto escalar no está secuenciado en relación con otro efecto secundario en el mismo objeto escalar, el comportamiento no está definido.

Por lo tanto, f(i=-1, i=-1)también es UB ya que cae bajo la misma regla, a pesar de que la intención del programador es (en mi humilde opinión) obvia e inequívoca.

Paul Draper también hace explícito en su conclusión que

¿Podría haber sido un comportamiento definido? Si. ¿Estaba definido? No.

lo que nos lleva a la pregunta de "¿por qué razón / propósito se f(i=-1, i=-1)dejó como comportamiento indefinido?"

por que razón / propósito

Aunque hay algunos descuidos (tal vez descuidados) en el estándar C ++, muchas omisiones están bien razonadas y tienen un propósito específico. Aunque soy consciente de que el propósito a menudo es "facilitar el trabajo del compilador-escritor" o "codificar más rápido", me interesaba principalmente saber si hay una buena razón para dejar el puesto f(i=-1, i=-1) como UB.

harmic y supercat proporcionan las principales respuestas que proporcionan una razón para la UB. Harmic señala que un compilador de optimización podría dividir las operaciones de asignación aparentemente atómicas en múltiples instrucciones de máquina, y que podría intercalar aún más esas instrucciones para una velocidad óptima. Esto podría llevar a resultados muy sorprendentes: ¡ itermina como -2 en su escenario! Por lo tanto, harmic demuestra cómo asignar el mismo valor a una variable más de una vez puede tener efectos nocivos si las operaciones no están secuenciadas.

supercat proporciona una exposición relacionada de las trampas de intentar f(i=-1, i=-1)hacer lo que parece que debería hacer. Señala que en algunas arquitecturas, existen restricciones estrictas contra múltiples escrituras simultáneas en la misma dirección de memoria. Un compilador podría tener dificultades para detectar esto si estuviéramos tratando con algo menos trivial que f(i=-1, i=-1).

davidf también proporciona un ejemplo de instrucciones entrelazadas muy similares a las de harmic.

Aunque cada uno de los ejemplos de harmic, supercat y davidf 'son algo artificiales, tomados en conjunto todavía sirven para proporcionar una razón tangible por la que f(i=-1, i=-1)debería ser un comportamiento indefinido.

Acepté la respuesta de Harmic porque hizo el mejor trabajo al abordar todos los significados del por qué, aunque la respuesta de Paul Draper abordó mejor la parte "por qué causa".

Otras respuestas

JohnB señala que si consideramos operadores de asignación sobrecargados (en lugar de simples escalares), también podemos tener problemas.

11 answers

346
harmic 2014-02-10 21:13.

Dado que las operaciones no están secuenciadas, no hay nada que diga que las instrucciones que realizan la asignación no se puedan intercalar. Podría ser óptimo hacerlo, según la arquitectura de la CPU. La página referenciada dice esto:

Si A no se secuencia antes de B y B no se secuencia antes de A, existen dos posibilidades:

  • las evaluaciones de A y B no están secuenciadas: pueden realizarse en cualquier orden y pueden superponerse (dentro de un solo hilo de ejecución, el compilador puede intercalar las instrucciones de la CPU que comprenden A y B)

  • Las evaluaciones de A y B tienen una secuencia indeterminada: pueden realizarse en cualquier orden pero no pueden superponerse: o A estará completo antes que B, o B estará completo antes que A. El orden puede ser el opuesto la próxima vez que la misma expresión se evalúa.

Eso por sí solo no parece que cause un problema, asumiendo que la operación que se está realizando es almacenar el valor -1 en una ubicación de memoria. Pero tampoco hay nada que decir que el compilador no puede optimizar eso en un conjunto separado de instrucciones que tienen el mismo efecto, pero que podrían fallar si la operación se intercala con otra operación en la misma ubicación de memoria.

Por ejemplo, imagine que es más eficiente poner a cero la memoria y luego disminuirla, en comparación con cargar el valor -1 pulg. Entonces esto:

f(i=-1, i=-1)

podría convertirse:

clear i
clear i
decr i
decr i

Ahora tengo -2.

Probablemente sea un ejemplo falso, pero es posible.

209
Paul Draper 2014-02-10 20:39.

En primer lugar, "objeto escalar" significa un tipo como una int, floato un puntero (ver ¿Qué es un objeto escalar en C ++? ).


En segundo lugar, puede parecer más obvio que

f(++i, ++i);

tendría un comportamiento indefinido. Pero

f(i = -1, i = -1);

es menos obvio.

Un ejemplo ligeramente diferente:

int i;
f(i = 1, i = -1);
std::cout << i << "\n";

¿Qué asignación ocurrió "última" i = 1, o i = -1? No está definido en el estándar. Realmente, ese medio ipodría ser 5(consulte la respuesta de Harmic para una explicación completamente plausible de cómo podría ser este el caso). O su programa podría segfault. O reformatea tu disco duro.

Pero ahora pregunta: "¿Qué pasa con mi ejemplo? Usé el mismo valor ( -1) para ambas asignaciones. ¿Qué podría no estar claro acerca de eso?"

Tiene razón ... excepto en la forma en que el comité de estándares de C ++ describió esto.

Si un efecto secundario en un objeto escalar no está secuenciado en relación con otro efecto secundario en el mismo objeto escalar, el comportamiento no está definido.

Ellos podrían haber hecho una excepción especial para su caso especial, pero no lo hicieron. (¿Y por qué habrían de hacerlo? ¿Qué utilidad tendría eso?) Entonces, itodavía podría serlo 5. O su disco duro podría estar vacío. Por tanto, la respuesta a su pregunta es:

Es un comportamiento indefinido porque no está definido cuál es el comportamiento.

(Esto merece un énfasis porque muchos programadores piensan que "indefinido" significa "aleatorio" o "impredecible". No es así; significa que no está definido por el estándar. El comportamiento podría ser 100% consistente y aún no estar definido).

¿Podría haber sido un comportamiento definido? Si. ¿Estaba definido? No. Por lo tanto, es "indefinido".

Dicho esto, "indefinido" no significa que un compilador formateará su disco duro ... significa que podría y seguirá siendo un compilador compatible con los estándares. Siendo realistas, estoy seguro de que g ++, Clang y MSVC harán lo que esperabas. Simplemente no "tendrían que hacerlo".


Una pregunta diferente podría ser ¿Por qué el comité de estándares de C ++ decidió que este efecto secundario no tuviera secuencia? . Esa respuesta involucrará la historia y las opiniones del comité. O ¿Qué tiene de bueno tener este efecto secundario sin secuenciar en C ++? , lo que permite cualquier justificación, sea o no el razonamiento real del comité de normas. Puede hacer esas preguntas aquí o en programmers.stackexchange.com.

27
Ingo 2014-02-11 00:23.

Una razón práctica para no hacer una excepción a las reglas solo porque los dos valores son iguales:

// config.h
#define VALUEA  1

// defaults.h
#define VALUEB  1

// prog.cpp
f(i = VALUEA, i = VALUEB);

Considere el caso de que esto estuviera permitido.

Ahora, unos meses después, surge la necesidad de cambiar

 #define VALUEB 2

Aparentemente inofensivo, ¿no? Y, sin embargo, de repente, prog.cpp ya no se compilaba. Sin embargo, creemos que la compilación no debería depender del valor de un literal.

En pocas palabras: no hay una excepción a la regla porque haría que la compilación exitosa dependiera del valor (más bien del tipo) de una constante.

EDITAR

¿Por qué f (i = -1, i = -1) es un comportamiento indefinido? que las expresiones constantes del formulario A DIV Bno están permitidas en algunos idiomas, cuando Bes 0, y hacen que la compilación falle. Por tanto, el cambio de una constante podría provocar errores de compilación en otro lugar. Lo cual es, en mi humilde opinión, lamentable. Pero ciertamente es bueno restringir tales cosas a lo inevitable.

12
davidf 2014-02-12 07:25.

La confusión es que almacenar un valor constante en una variable local no es una instrucción atómica en cada arquitectura en la que C está diseñado para ejecutarse. El procesador en el que se ejecuta el código importa más que el compilador en este caso. Por ejemplo, en ARM donde cada instrucción no puede transportar una constante completa de 32 bits, almacenar un int en una variable necesita más de una instrucción. Ejemplo con este pseudocódigo donde solo puede almacenar 8 bits a la vez y debe trabajar en un registro de 32 bits, i es un int32:

reg = 0xFF; // first instruction
reg |= 0xFF00; // second
reg |= 0xFF0000; // third
reg |= 0xFF000000; // fourth
i = reg; // last

Puede imaginar que si el compilador quiere optimizarlo, puede intercalar la misma secuencia dos veces y no sabe qué valor se escribirá en i; y digamos que no es muy listo:

reg = 0xFF;
reg |= 0xFF00;
reg |= 0xFF0000;
reg = 0xFF;
reg |= 0xFF000000;
i = reg; // writes 0xFF0000FF == -16776961
reg |= 0xFF00;
reg |= 0xFF0000;
reg |= 0xFF000000;
i = reg; // writes 0xFFFFFFFF == -1

Sin embargo, en mis pruebas, gcc tiene la amabilidad de reconocer que el mismo valor se usa dos veces y lo genera una vez y no hace nada extraño. Obtengo -1, -1 Pero mi ejemplo sigue siendo válido, ya que es importante tener en cuenta que incluso una constante puede no ser tan obvia como parece.

11
supercat 2014-02-11 06:55.

El comportamiento se especifica comúnmente como indefinido si existe alguna razón concebible por la cual un compilador que intentaba ser "útil" podría hacer algo que causaría un comportamiento totalmente inesperado.

En el caso de que una variable se escriba varias veces sin nada que garantice que las escrituras suceden en momentos distintos, algunos tipos de hardware pueden permitir que se realicen varias operaciones de "almacenamiento" simultáneamente en diferentes direcciones utilizando una memoria de doble puerto. Sin embargo, algunas memorias de doble puerto prohíben expresamente el escenario en el que dos tiendas acceden a la misma dirección simultáneamente, independientemente de si los valores escritos coinciden o no . Si un compilador de una máquina de este tipo nota dos intentos no secuenciados para escribir la misma variable, podría negarse a compilar o asegurarse de que las dos escrituras no se puedan programar simultáneamente. Pero si uno o ambos accesos es a través de un puntero o referencia, es posible que el compilador no siempre pueda decir si ambas escrituras pueden llegar a la misma ubicación de almacenamiento. En ese caso, podría programar las escrituras simultáneamente, provocando una trampa de hardware en el intento de acceso.

Por supuesto, el hecho de que alguien pueda implementar un compilador de C en una plataforma de este tipo no sugiere que dicho comportamiento no deba definirse en plataformas de hardware cuando se usan almacenes de tipos lo suficientemente pequeños como para procesarse atómicamente. Tratar de almacenar dos valores diferentes de forma no secuenciada podría causar rarezas si un compilador no lo sabe; por ejemplo, dado:

uint8_t v;  // Global

void hey(uint8_t *p)
{
  moo(v=5, (*p)=6);
  zoo(v);
  zoo(v);
}

si el compilador en línea la llamada a "moo" y puede decir que no modifica "v", podría almacenar un 5 av, luego almacenar un 6 to * p, luego pasar 5 a "zoo", y luego pasar el contenido de v a "zoo". Si "zoo" no modifica "v", no debería haber forma de que las dos llamadas pasen valores diferentes, pero eso podría suceder fácilmente de todos modos. Por otro lado, en los casos en los que ambas tiendas escribieran el mismo valor, tal rareza no podría ocurrir y en la mayoría de las plataformas no habría una razón sensata para que una implementación hiciera algo extraño. Desafortunadamente, algunos escritores de compiladores no necesitan ninguna excusa para comportamientos tontos más allá de "porque el Estándar lo permite", por lo que incluso esos casos no son seguros.

9
Amadan 2014-02-10 20:42.

El hecho de que el resultado sea el mismo en la mayoría de las implementaciones en este caso es incidental; el orden de evaluación aún no está definido. Considere f(i = -1, i = -2): aquí, el orden importa. La única razón por la que no importa en su ejemplo es el accidente que tienen ambos valores -1.

Dado que la expresión se especifica como una con un comportamiento indefinido, un compilador que cumpla con las normas malintencionadas podría mostrar una imagen inapropiada cuando evalúe f(i = -1, i = -1)y anule la ejecución, y aún así se considere completamente correcto. Afortunadamente, ningún compilador que yo sepa lo hace.

8
Martin J. 2014-02-10 20:52.

Me parece que la única regla relacionada con la secuenciación de la expresión del argumento de la función está aquí:

3) Al llamar a una función (si la función está en línea o no, y si se usa o no la sintaxis de llamada de función explícita), cada cálculo de valor y efecto secundario asociado con cualquier expresión de argumento, o con la expresión de sufijo que designa la función llamada, secuenciado antes de la ejecución de cada expresión o declaración en el cuerpo de la función llamada.

Esto no define la secuenciación entre expresiones de argumentos, por lo que terminamos en este caso:

1) Si un efecto secundario en un objeto escalar no está secuenciado en relación con otro efecto secundario en el mismo objeto escalar, el comportamiento no está definido.

En la práctica, en la mayoría de los compiladores, el ejemplo que citó funcionará bien (en lugar de "borrar su disco duro" y otras consecuencias teóricas de comportamiento indefinido).
Sin embargo, es una responsabilidad, ya que depende del comportamiento específico del compilador, incluso si los dos valores asignados son los mismos. Además, obviamente, si intentara asignar valores diferentes, los resultados serían "verdaderamente" indefinidos:

void f(int l, int r) {
    return l < -1;
}
auto b = f(i = -1, i = -2);
if (b) {
    formatDisk();
}
8
AlexD 2017-09-13 12:17.

C ++ 17 define reglas de evaluación más estrictas. En particular, secuencia los argumentos de la función (aunque en un orden no especificado).

N5659 §4.6:15
Las evaluaciones A y B tienen una secuencia indeterminada cuando A se secuencia antes que B o B se secuencia antes que A , pero no se especifica cuál. [ Nota : Las evaluaciones en secuencia indeterminada no pueden superponerse, pero cualquiera de las dos se podría ejecutar primero. - nota final ]

N5659 § 8.2.2:5
La inicialización de un parámetro, incluido todo cálculo de valor asociado y efecto secundario, se secuencia de forma indeterminada con respecto a la de cualquier otro parámetro.

Permite algunos casos que antes serían UB:

f(i = -1, i = -1); // value of i is -1
f(i = -1, i = -2); // value of i is either -1 or -2, but not specified which one
5
JohnB 2014-02-11 04:08.

El operador de asignación podría estar sobrecargado, en cuyo caso el orden podría importar:

struct A {
    bool first;
    A () : first (false) {
    }
    const A & operator = (int i) {
        first = !first;
        return * this;
    }
};

void f (A a1, A a2) {
    // ...
}


// ...
A i;
f (i = -1, i = -1);   // the argument evaluated first has ax.first == true
2
Peng Zhang 2014-02-10 20:56.

Esto solo responde al "No estoy seguro de qué podría significar" objeto escalar "además de algo como un int o un flotante".

Yo interpretaría el "objeto escalar" como una abreviatura de "objeto de tipo escalar", o simplemente "variable de tipo escalar". Entonces, pointer, enum(constante) son de tipo escalar.

Este es un artículo de MSDN sobre tipos escalares .

2
polkovnikov.ph 2015-04-02 01:03.

En realidad, hay una razón para no depender del hecho de que el compilador verificará que ise le asigne el mismo valor dos veces, de modo que sea posible reemplazarlo con una sola asignación. ¿Y si tenemos algunas expresiones?

void g(int a, int b, int c, int n) {
    int i;
    // hey, compiler has to prove Fermat's theorem now!
    f(i = 1, i = (ipow(a, n) + ipow(b, n) == ipow(c, n)));
}

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?

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.

Un minorista está eliminando su sección de tallas grandes y mezclando tallas más grandes con todo lo demás

Un minorista está eliminando su sección de tallas grandes y mezclando tallas más grandes con todo lo demás

Un minorista está enlatando su sección de tallas grandes. Pero no están tomando la categoría solo en línea o descontinuándola por completo.

La mejor forma de guardar animales de peluche es dentro de un puf

La mejor forma de guardar animales de peluche es dentro de un puf

Entiendo totalmente, completamente si tienes una relación difícil con los animales de peluche. Son lindos, tienen valor sentimental y es difícil separarse de ellos.

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