Itzik984
Me enfrenté a una pregunta difícil (en mi opinión). necesitaba comparar dos direcciones MACde la manera más eficiente.
El único pensamiento que cruzó mi mente en ese momento fue la solución trivial: una for
loop, y comparando locaciones, y así lo hice, pero el entrevistador apuntaba al casting.
La definición MAC:
typedef struct macA {
char data[6];
} MAC;
Y la función es (la que me pidieron implementar):
int isEqual(MAC* addr1, MAC* addr2)
{
int i;
for(i = 0; i<6; i++)
{
if(addr1->data[i] != addr2->data[i])
return 0;
}
return 1;
}
Pero como se mencionó, él apuntaba al casting.
Es decir, para convertir de alguna manera la dirección MAC dada a un int, comparar ambas direcciones y regresar.
Pero al lanzar, int int_addr1 = (int)addr1;
, solo se emitirán cuatro bytes, ¿verdad? ¿Debería revisar los restantes? ¿Significa las ubicaciones 4 y 5?
Ambas cosas char
y int
son tipos enteros, por lo que la conversión es legal, pero ¿qué sucede en la situación descrita?
Si él está realmente insatisfecho con este enfoque (que es esencialmente un pedo cerebral, ya que no está comparando megabytes o gigabytes de datos, por lo que uno no debe preocuparse por la “eficiencia” y la “velocidad” en este caso), simplemente dígale que confía en la calidad y la velocidad de la biblioteca estándar:
int isEqual(MAC* addr1, MAC* addr2)
{
return memcmp(&addr1->data, &addr2->data, sizeof(addr1->data)) == 0;
}
-
@H2CO3, puede escribir una publicación reescribiendo un subsistema OS i/o y obtener 35 puntos. Golpea a un entrevistador, un proceso que todos odian inherentemente, ¡y 150!
– Pato
31/12/2013 a las 15:00
-
@H2CO3: Si bien es cierto que
data
es una matriz y no un puntero,memcmp(addr1->data, addr2->data, sizeof(addr1->data))
sigue siendo correcto (debido a la descomposición), ¿no?– leyendas2k
31 de diciembre de 2013 a las 15:06
-
@haccks: Para mí, las águilas solo representan que “puedes iniciar sesión en cualquier momento, pero nunca puedes cerrar sesión”…
– KerrekSB
31 de diciembre de 2013 a las 15:17
-
@chux Porque puede haber relleno al final de la estructura que contiene bytes de relleno no especificados. Eso daría como resultado falsos negativos.
– usuario529758
31 de diciembre de 2013 a las 21:32
-
@Lee ¿Eh qué? “memcmp de dos enteros es ineficiente” no es cierto tal como está (consulte la discusión anterior). Además, “código sensato” == código que no es innecesariamente “inteligente” y no depende de UB si no es necesario. Una propiedad del código sano es la confianza en la biblioteca estándar. ¿Ahora ves?
– usuario529758
31 de diciembre de 2013 a las 23:55
Kerrek SB
Si su entrevistador exige que produzca un comportamiento indefinido, probablemente buscaría un trabajo en otro lugar.
El enfoque inicial correcto sería almacenar la dirección MAC en algo como un uint64_t
, al menos en la memoria. Entonces las comparaciones serían triviales e implementables de manera eficiente.
-
@ltzik984 No, lo más probable es que no tenga idea de que este es un comportamiento indefinido.
– usuario529758
31 de diciembre de 2013 a las 14:43
-
@Abyx: ¿Por qué no publicar como respuesta lo que cree que se debe hacer, y podemos ver si es UB?
– KerrekSB
31 de diciembre de 2013 a las 14:55
-
@Abyx Conduce a UB. En el mismo momento en que elimina la referencia del puntero a un tipo incompatible.
– usuario529758
31 de diciembre de 2013 a las 14:59
-
@Abyx: No conoces a C, pero ¿me rechazas por no sugerir algo que estaría mal? :-S
– KerrekSB
31 de diciembre de 2013 a las 15:14
-
@rm5248: una dirección MAC es solo un numero Es como si insistieras en diseñar tu propio entero de 21 bits para caracteres de texto, en lugar de usar el mismo
char32_t
.– KerrekSB
31 de diciembre de 2013 a las 23:15
Glenn Teitelbaum
Hora del vaquero:
typedef struct macA {
char data[6];
} MAC;
typedef struct sometimes_works {
long some;
short more;
} cast_it
typedef union cowboy
{
MAC real;
cast_it hack;
} cowboy_cast;
int isEqual(MAC* addr1, MAC* addr2)
{
assert(sizeof(MAC) == sizeof(cowboy_cast)); // Can't be bigger
assert(sizeof(MAC) == sizeof(cast_it)); // Can't be smaller
if ( ( ((cowboy_cast *)addr1)->hack.some == ((cowboy_cast *)addr2)->hack.some )
&& ( ((cowboy_cast *)addr1)->hack.more == ((cowboy_cast *)addr2)->hack.more ) )
return (0 == 0);
return (0 == 42);
}
-
No estoy seguro de cómo se gana mucho aquí usando la unión. Quiero decir, si tiene problemas de alineación con su MAC, aún los tendrá. Mientras tanto, si no tiene problemas de alineación, entonces todavía confía en que el tamaño de (MAC) sea 6 y/o el relleno esté en el lugar correcto. Parece que los mismos riesgos que corre lanzan el MAC/punto de datos directamente.
– Richard Plunkett
31 de diciembre de 2013 a las 17:02
-
@RichardPlunkett Incluí el elenco de vaqueros porque a veces aparece en entrevistas o código heredado, y si tiene beneficios sobre el elenco directo o
memcmp
no es tan importante como conocer este enfoque y probablemente evitarlo. Pero si no lo has visto, no puedes decir por qué.– Glenn Teitelbaum
31 de diciembre de 2013 a las 17:08
-
@RichardPlunkett No. Las uniones garantizan una alineación correcta. Desde C99, el juego de palabras basado en unión es legal (ya no es UB), a diferencia de los alias a través de punteros a tipos incompatibles.
– usuario529758
31 de diciembre de 2013 a las 21:34
-
¿No debería ser así?
typedef struct sometimes_works { uint32_t some; uint16_t more; } cast_it;
por portabilidad?– tomlogic
1 de enero de 2014 a las 1:11
-
@ H2CO3, no creo que un sindicato garantice la alineación aquí. Quiero decir, si declara una variable cowboy_cast, se alineará correctamente para esto, pero no puede garantizar que ninguna estructura MAC miscelánea tenga la alineación correcta para un cowboy_cast, y se nos pasa un puntero MAC.
– Richard Plunkett
1 de enero de 2014 a las 1:47
A priori
No hay nada de malo en una implementación eficiente, por lo que sabe, se ha determinado que se trata de un código activo que se llama muchas veces. Y en cualquier caso, está bien que las preguntas de la entrevista tengan restricciones extrañas.
AND lógico es a priori una instrucción de bifurcación debido a la evaluación de cortocircuito, incluso si no se compila de esta manera, así que evitémoslo, no lo necesitamos. Tampoco necesitamos convertir nuestro valor devuelto en un verdadero bool (verdadero o falsono 0 o cualquier cosa que no sea cero).
Aquí hay una solución rápida en 32 bits: XOR capturará las diferencias, O registrará la diferencia en ambas partes, y NO negará la condición en IGUAL, no DESIGUAL. LHS y RHS son cálculos independientes, por lo que un procesador superescalar puede hacer esto en paralelo.
int isEqual(MAC* addr1, MAC* addr2)
{
return ~((*(int*)addr2 ^ *(int*)addr1) | (int)(((short*)addr2)[2] ^ ((short*)addr1)[2]));
}
EDITAR
El propósito del código anterior era mostrar que esto se podía hacer de manera eficiente sin bifurcarse. Los comentarios han señalado que C++ clasifica esto como un comportamiento indefinido. Si bien es cierto, VS maneja esto bien. Sin cambiar la definición de estructura del entrevistador y la firma de la función, para evitar un comportamiento indefinido, se debe hacer una copia adicional. Entonces, la forma de comportamiento no indefinido sin ramificación pero con una copia adicional sería la siguiente:
int isEqual(MAC* addr1, MAC* addr2)
{
struct IntShort
{
int i;
short s;
};
union MACU
{
MAC addr;
IntShort is;
};
MACU u1;
MACU u2;
u1.addr = *addr1; // extra copy
u2.addr = *addr2; // extra copy
return ~((u1.is.i ^ u2.is.i) | (int)(u1.is.s ^ u2.is.s)); // still no branching
}
RichardPlunkett
Esto funcionaría en la mayoría de los sistemas y sería más rápido que su solución.
int isEqual(MAC* addr1, MAC* addr2)
{
return ((int32*)addr1)[0] == ((int32*)addr2)[0] && ((int16*)addr1)[2] == ((int16*)addr2)[2];
}
estaría en línea muy bien también, podría ser útil en el centro del ciclo en un sistema donde puede verificar que los detalles sean viables.
-
Y así la hidra del Comportamiento Indefinido asoma varias de sus muchas cabezas.
– KerrekSB
31 de diciembre de 2013 a las 15:01
-
Si bien es cierto, valdría la pena mencionar que esto se basa en un comportamiento indefinido.
– usuario529758
31 de diciembre de 2013 a las 15:01
-
Al menos redefine la estructura como unión con uint16_t[3] o uint32_t+uint16_t si se permite relleno adicional y compare esos campos para evitar UB. Por supuesto, muchos compiladores son lo suficientemente inteligentes como para optimizar adecuadamente memcmp() de tamaño fijo, especialmente si se les da una pista de alineación.
– doynax
31 de diciembre de 2013 a las 15:06
-
los
int16
la comparación debe estar en el tercer elemento (índice 2), no en el segundo.– Benjamín Lindley
31 de diciembre de 2013 a las 15:08
-
@doynax: el problema más obvio es que la plataforma puede requerir almacenamiento alineado para operaciones con enteros, por lo que si su puntero de carácter no satisface eso, su CPU puede generar una excepción. x86 no tiene ese problema, pero hay otras plataformas que sí.
– KerrekSB
31 de diciembre de 2013 a las 16:16
chux – Reincorporar a Monica
Solución de fundición no portátil.
En una plataforma que uso (basada en PIC24), hay un tipo int48
por lo que haciendo una suposición segura char
es de 8 bits y los requisitos de alineación habituales:
int isEqual(MAC* addr1, MAC* addr2) {
return *((int48_t*) &addr1->data) == *((int48_t*) &addr2->data);
}
Por supuesto, esto no se puede usar en muchas plataformas, pero también lo son una serie de soluciones que tampoco son portátiles, dependiendo de lo que se suponga. int
Talla, no padding
etc.
La mejor solución portátil (y razonablemente rápida dado un buen compilador) es el memcmp()
ofrecido por @H2CO3.
Ir a un nivel de diseño más alto y usar un tipo entero lo suficientemente amplio como uint64_t
en vez de struct macA
como sugiere Kerrek SB, es muy atractivo.
-
Y así la hidra del Comportamiento Indefinido asoma varias de sus muchas cabezas.
– KerrekSB
31 de diciembre de 2013 a las 15:01
-
Si bien es cierto, valdría la pena mencionar que esto se basa en un comportamiento indefinido.
– usuario529758
31 de diciembre de 2013 a las 15:01
-
Al menos redefine la estructura como unión con uint16_t[3] o uint32_t+uint16_t si se permite relleno adicional y compare esos campos para evitar UB. Por supuesto, muchos compiladores son lo suficientemente inteligentes como para optimizar adecuadamente memcmp() de tamaño fijo, especialmente si se les da una pista de alineación.
– doynax
31 de diciembre de 2013 a las 15:06
-
los
int16
la comparación debe estar en el tercer elemento (índice 2), no en el segundo.– Benjamín Lindley
31 de diciembre de 2013 a las 15:08
-
@doynax: el problema más obvio es que la plataforma puede requerir almacenamiento alineado para operaciones con enteros, por lo que si su puntero de carácter no satisface eso, su CPU puede generar una excepción. x86 no tiene ese problema, pero hay otras plataformas que sí.
– KerrekSB
31 de diciembre de 2013 a las 16:16
Para escribir juegos de palabras correctamente, debe usar una unión. De lo contrario, romperá las reglas estrictas de creación de alias que siguen ciertos compiladores, y el resultado será indefinido.
int EqualMac( MAC* a , MAC* b )
{
union
{
MAC m ;
uint16_t w[3] ;
} ua , ub ;
ua.m = *a ;
ub.m = *b ;
if( ua.w[0] != ub.w[0] )
return 0 ;
if( ua.w[1] != ub.w[1] )
return 0 ;
if( ua.w[2] != ub.w[2] )
return 0 ;
return 1 ;
}
Según C99, es seguro leer de un miembro de la unión que no es el último que se usó para almacenar un valor en él.
Si el miembro utilizado para leer el contenido de un objeto de unión no es el mismo que el último miembro utilizado para almacenar un valor en el objeto, la parte apropiada de la representación de objeto del valor se reinterpreta como una representación de objeto en el nuevo tipo como descrito en 6.2.6 (un proceso a veces llamado “tipo de juego de palabras”). Esto podría ser una representación trampa.
Lo siento, pero ¿quién dijo que tenías que hacer algún casting de int? quería que hicieras una comparación de caracteres
– Noam Rathaus
31 de diciembre de 2013 a las 14:46
¿Está destinado a un sistema integrado?
–Michael Hampton
31 de diciembre de 2013 a las 18:25
dado que el tamaño de una MAC es de 48 bits (6 bytes) por definición, tiene sentido #define MAC_SIZE (6).
– ChuckCottrill
31 de diciembre de 2013 a las 18:58
Por lo que vale, cuando hago este tipo de preguntas, ¡lo cual trato de no hacer! — Por lo general, no busco una sola respuesta, sino qué preguntas hace el solicitante sobre las limitaciones y prioridades, y si puede discutir por qué eligió una respuesta en lugar de otra.
– keshlam
1 de enero de 2014 a las 5:20