C工作面试 - 铸造和比较

时间:2013-12-31 14:40:15

标签: c casting char int

我遇到了一个棘手的(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?

charint都是整数类型,因此转换是合法的,但会发生什么 在描述的情况?

10 个答案:

答案 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 *)的转换。 无论如何不好的问题。