我遇到了一个棘手的(IMO)问题。我需要以最有效的方式比较两个MAC addresses。
在那一刻我心中想到的唯一想法是琐碎的解决方案 - 一个for
循环,并比较位置,所以我做了,但面试官的目标是投射。
MAC定义:
typedef struct macA {
char data[6];
} MAC;
功能是(我被要求实施的那个):
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;
}
但如上所述,他的目标是施展。
意思是,以某种方式将给定的MAC地址转换为int,比较两个地址,然后返回。
但是在投射int int_addr1 = (int)addr1;
时,只会投入四个字节,对吧?我应该检查其余的吗?含义位置4和5?
char
和int
都是整数类型,因此转换是合法的,但会发生什么
在描述的情况?
答案 0 :(得分:112)
如果他真的不满意这种方法(这实际上是一个脑屁,因为你没有比较兆字节或千兆字节的数据,所以在这种情况下,人们不应该真的担心“效率”和“速度” ),告诉他你相信标准库的质量和速度:
int isEqual(MAC* addr1, MAC* addr2)
{
return memcmp(&addr1->data, &addr2->data, sizeof(addr1->data)) == 0;
}
答案 1 :(得分:44)
如果你的面试官要求你产生不明确的行为,我可能会在其他地方寻找工作。
正确的初始方法是将MAC地址存储在类似uint64_t
的内容中,至少在内存中。然后比较将是微不足道的,并且可以有效地实施。
答案 2 :(得分:16)
牛仔时间:
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);
}
答案 3 :(得分:13)
有效的实现没有任何问题,因为你知道这已被确定为多次调用的热代码。在任何情况下,面试问题都可以有奇怪的限制。
逻辑AND是由于短路评估的先验分支指令,即使它不以这种方式编译,所以让我们避免它,我们不需要它。我们也不需要将返回值转换为真正的bool( true 或 false ,而不是 0 或任何非零的< / em>的)。
以下是32位的快速解决方案: XOR将捕获差异,OR将记录两个部分的差异,而NOT将否定条件为EQUALS,而不是UNEQUAL。 LHS和RHS是独立的计算,因此超标量处理器可以并行执行此操作。
int isEqual(MAC* addr1, MAC* addr2)
{
return ~((*(int*)addr2 ^ *(int*)addr1) | (int)(((short*)addr2)[2] ^ ((short*)addr1)[2]));
}
修改强>
上述代码的目的是表明这可以在没有分支的情况下有效地完成。评论指出这个C ++将其归类为未定义的行为。虽然如此,VS处理这个问题。在不更改访问者的结构定义和函数签名的情况下,为了避免未定义的行为,必须进行额外的复制。因此,没有分支但具有额外副本的非未定义行为方式如下:
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
}
答案 4 :(得分:4)
这适用于大多数系统,并且比您的解决方案更快。
int isEqual(MAC* addr1, MAC* addr2)
{
return ((int32*)addr1)[0] == ((int32*)addr2)[0] && ((int16*)addr1)[2] == ((int16*)addr2)[2];
}
也很好地内联,在系统的循环中心可以很方便,你可以检查细节是否可行。
答案 5 :(得分:3)
非便携式铸造解决方案。
在我使用的平台(基于PIC24)中,有一个类型int48
,因此安全假设char
是8位和通常的对齐要求:
int isEqual(MAC* addr1, MAC* addr2) {
return *((int48_t*) &addr1->data) == *((int48_t*) &addr2->data);
}
当然,这在许多平台上都无法使用,但随后也有许多不可移植的解决方案,具体取决于假定的int
大小,no padding
等。
最高的可移植解决方案(并且在编译良好的情况下相当快)是@ H2CO3提供的memcmp()
。
如Kerrek SB所建议的那样,使用更宽的设计级别并使用足够宽的整数类型(如uint64_t
而不是struct macA
)非常有吸引力。
答案 6 :(得分:1)
要正确进行打字,你必须使用联合。否则,您将破坏某些编译器遵循的规则严格别名,结果将是未定义的。
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 ;
}
根据C99,可以安全地从不是最后一个用于存储值的工会成员中读取。
如果用于读取union对象内容的成员与上次用于在对象中存储值的成员不同,则该值的对象表示的相应部分将被重新解释为对象表示形式。 6.2.6中描述的新类型(有时称为“类型双关”的过程)。这可能是陷阱表示。
答案 7 :(得分:0)
你有一个MAC结构(包含一个6字节的数组),
typedef struct {
char data[6];
} MAC;
这与关于typedef for fixed length byte array的这篇文章一致。
天真的方法是假设MAC地址是字对齐的(这可能是面试官想要的),尽管不能保证。
typedef unsigned long u32;
typedef signed long s32;
typedef unsigned short u16;
typedef signed short s16;
int
MACcmp(MAC* mac1, MAC* mac2)
{
if(!mac1 || !mac2) return(-1); //check for NULL args
u32 m1 = *(u32*)mac1->data;
U32 m2 = *(u32*)mac2->data;
if( m1 != m2 ) return (s32)m1 - (s32)m2;
u16 m3 = *(u16*)(mac1->data+4);
u16 m2 = *(u16*)(mac2->data+4);
return (s16)m3 - (s16)m4;
}
稍微更安全的是将char [6]解释为short [3](MAC更可能在偶数字节边界上对齐而不是奇数),
typedef unsigned short u16;
typedef signed short s16;
int
MACcmp(MAC* mac1, MAC* mac2)
{
if(!mac1 || !mac2) return(-1); //check for NULL args
u16* p1 = (u16*)mac1->data;
u16* p2 = (u16*)mac2->data;
for( n=0; n<3; ++n ) {
if( *p1 != *p2 ) return (s16)*p1 - (s16)*p2;
}
return(0);
}
不假设,并复制到单词对齐存储,但这里进行类型转换的唯一原因是满足面试官,
typedef unsigned short u16;
typedef signed short s16;
int
MACcmp(MAC* mac1, MAC* mac2)
{
if(!mac1 || !mac2) return(-1); //check for NULL args
u16 m1[3]; u16 p2[3];
memcpy(m1,mac1->data,6);
memcpy(m2,mac2->data,6);
for( n=0; n<3; ++n ) {
if( m1[n] != m2[n] ) return (s16)m1[n] - (s16)m2[n];
}
return(0);
}
为自己节省大量工作,
int
MACcmp(MAC* mac1, MAC* mac2)
{
if(!mac1 || !mac2) return(-1);
return memcmp(mac1->data,mac2->data,6);
}
答案 8 :(得分:0)
函数memcmp最终会自行完成循环。因此,通过使用它,您基本上只会降低效率(由于额外的函数调用)。
这是一个可选的解决方案:
typedef struct
{
int x;
short y;
}
MacAddr;
int isEqual(MAC* addr1, MAC* addr2)
{
return *(MacAddr*)addr1 == *(MacAddr*)addr2;
}
编译器很可能将此代码转换为两个比较,因为MacAddr结构包含两个字段。
Cavity:除非你的CPU支持未对齐的加载/存储操作,否则addr1和addr2必须对齐到4个字节(即,它们必须位于可被4整除的地址中)。否则,执行该函数时很可能会发生内存访问冲突。
您可以将结构划分为3个字段,每个字段2个字节,或6个字段,每个字节1个字节(分别将对齐限制减少到2或1)。但请记住,源代码中的单个比较不一定是可执行映像中的单个比较(即,在运行时)。
BTW,如果在CPU管道中需要更多的“nops”,那么未对齐的加载/存储操作本身可能会增加运行时延迟。这实际上是CPU架构的问题,我怀疑它们是否意味着在你的求职面试中“深入研究”。但是,为了断言已编译的代码不包含此类操作(如果它们确实“昂贵”),您可以确保变量始终与8个字节对齐 AND 添加#pragma(编译器指令)告诉编译器“不要担心这个”。答案 9 :(得分:0)
可能他记得使用unsigned char的MAC定义并且正在考虑:
int isEqual(MAC* addr1, MAC* addr2) { return strncmp((*addr1).data,(*addr2).data,6)==0; }
表示从(unsigned char *)到(char *)的转换。 无论如何不好的问题。