昨天,在一次访谈中,我被要求测试一个数字中的第5位(测试它是否开启和关闭)以及下面我是如何做到的.
int number = 16;
int mask   = 1<<5;

if ((number & mask) == 0)
    printf("Bit is off");
else
    printf("its on");

但他随后要求我优化这段代码并不使用这个特殊的掩码.

所以我的问题是我在这段代码中可以使用的其他掩码?

解决方法

也许面试官想看看你对一个简单挑战的反应.或者只是想知道你是否真的了解C,并坚持自己的立场?也许面试官想知道你是否知道非零是真的,因此测试你对C的理解深度?或者也许你是否可以在头脑中做二进制到十六进制?

恕我直言的采访不仅仅是代码.它们做起来很昂贵.我总是试图给人一个清晰的印象,通过书面沟通,甚至通过电话很难做到.毕竟,其中一些人将与新兵一起工作!

最紧凑,可能最快的可能是:

int number = 16;  // is this really what they gave?

printf((number & 0x20)?"its on":"Bit is off"); // did they mean 5th or bit 5?

编辑:

我编写了原始方法和我的替代方案,并为ARM Coretx-M3 / 4编译了它(这是我目前正在编写的处理器).它是用-O3编译的.然后我反汇编每个编译文件(使用objdump)来获取汇编程序.我是这样做的,因为gcc -S的输出很大;这包括汇编器和链接器的大量额外信息,这使得查找代码变得更加困难.

我用dummy_printf替换了printf以避免#including stdio.h,这增加了更多的噪音. dummy_printf与printf不完全相同,只需要一个参数,但它会使输出保持简短:-)

源(附加的所有7个文件使其更易于阅读)位于:
http://pastebin.com/PTeApu9n

得到的objdump连接输出(对于每个.o)位于:
http://pastebin.com/kHAmakE3

如您所见,原始编译为:

void original_bit5(int number) {
    int mask = 1<<5;

    if ((number & mask) == 0)
   0:   f010 0f20   tst.w   r0,#32
   4:   d005        beq.n   1a <original_bit5+0x1a>
        dummy_printf("Bit is off");
    else
        dummy_printf("its on"); 
   6:   f240 0000   movw    r0,#0
   a:   f2c0 0000   movt    r0,#0
   e:   f7ff bffe   b.w 0 <dummy_printf>

void original_bit5(int number) {
    int mask = 1<<5;

    if ((number & mask) == 0)
        dummy_printf("Bit is off");
  12:   f240 0000   movw    r0,#0
  16:   f2c0 0000   movt    r0,#0
  1a:   f7ff bffe   b.w 0 <dummy_printf>
  1e:   bf00        nop

我认为对dummy_printf的调用是使用尾调用链接,即dummy_printf不会返回此函数.效率很高!

没有函数入口代码,因为前四个函数参数在寄存器r0-r3中传递.

您无法在r0中看到正在加载的两个字符串的地址.那是因为这没有联系.

你可以看到:

int mask = 1<<5;    
if ((number & mask) == 0)

编译为:

0:   f010 0f20   tst.w   r0,#32
   4:   d005        beq.n   1a <original_bit5+0x1a>

因此1<<< 5和(... == 0)被编译成更直接和有效的指令序列.有一个分支到适当的dummy_printf调用. 我的代码编译为:

void my_bit5(int number) {
    dummy_printf((number & 0x20)?"its on":"Bit is off");    
   0:   f240 0200   movw    r2,#0
   4:   f240 0300   movw    r3,#0
   8:   f010 0f20   tst.w   r0,#32
   c:   f2c0 0200   movt    r2,#0
  10:   f2c0 0300   movt    r3,#0
  14:   bf14        ite ne
  16:   4610        movne   r0,r2
  18:   4618        moveq   r0,r3
  1a:   f7ff bffe   b.w 0 <dummy_printf>
  1e:   bf00        nop

这似乎也得到尾调用优化,即没有返回此函数,因为不需要一个,dummy_printf的返回将直接返回main()

你看不到的是两个寄存器,r2和r2将包含两个字符串的地址.那是因为这没有联系.

如您所见,有一个条件执行指令’ite’,它将寄存器r2或寄存器r3加载到参数寄存器r0中.所以这段代码中没有分支.

对于带流水线的简单处理器,这可能非常有效.在简单的流水线处理器上,分支可能会导致“管道”停顿,同时清除管道的某些部分.这因处理器而异.所以我假设gcc已经做对了,并且生成了比执行分支更好的代码序列.我没有检查过.

在伦丁的激励下,我想出了这个:

void union_bit5(int number) {
    union { int n; struct { unsigned :5; unsigned bit :1; }; } tester;
    tester.n = number;

    if (tester.bit)
        dummy_printf("Bit is on");
    else
        dummy_printf("its off");    
}

它没有明确地包括掩码或位移.它几乎肯定是编译器依赖的,你必须测试它以确保它的工作(glerk! – (

ARM的gcc生成相同的代码(bne vs beq,但可以调整)作为OP的解决方案,因此没有优化,但它删除了掩码:

void union_bit5(int number) {
    union { int n; struct { unsigned :5; unsigned bit :1; }; } tester;
    tester.n = number;

    if (tester.bit)
   0:   f010 0f20   tst.w   r0,#32
   4:   d105        bne.n   1a <union_bit5+0x1a>
        dummy_printf("Bit is on");
    else
        dummy_printf("its off");    
   6:   f240 0000   movw    r0,#0
   e:   f7ff bffe   b.w 0 <dummy_printf>
void union_bit5(int number) {
    union { int n; struct { unsigned :5; unsigned bit :1; }; } tester;
    tester.n = number;

    if (tester.bit)
        dummy_printf("Bit is on");
  12:   f240 0000   movw    r0,#0
  1a:   f7ff bffe   b.w 0 <dummy_printf>
  1e:   bf00        nop

物有所值:

(number & 0x20)? dummy_printf("its on") : dummy_printf("Bit is off");

ARM的gcc生成与OP完全相同的代码.它生成分支,而不是条件指令.

摘要:

>原始代码被编译为非常有效的指令序列
>三元组……?…:…运算符可以编译为不涉及ARM Cortex-M3 / 4上的分支的代码,但也可以生成正常的分支指令.
>在这种情况下,编写比原始代码更高效的代码很困难:-)

我要补充一点,恕我直言,与一点测试相比,做一个printf的成本是如此巨大,担心优化一点测试的问题太小了;它是失败的Amdahl’s Law.比特测试的适当策略是确保使用-O3或-Os.

如果你想做一些有点不正常的事情(特别是对于这样一个微不足道的问题),但可能会让采访者想到的不同,那就为每个字节值创建一个查找表. (我不指望它会更快……)

#define BIT5(val) (((val)&0x20)?1:0)
const unsigned char bit5[256] = {
BIT5(0x00),BIT5(0x01),BIT5(0x02),BIT5(0x03),BIT5(0x04),BIT5(0x05),BIT5(0x06),BIT5(0x07),// ... you get the idea ...
BIT5(0xF8),BIT5(0xF9),BIT5(0xFA),BIT5(0xFB),BIT5(0xFC),BIT5(0xFD),BIT5(0xFE),BIT5(0xFF)
};

//...
if (bit5[(unsigned char)number]) {
    printf("its on");
} else {
    printf("Bit is off");
}

如果在例如外围寄存器中存在某些复杂的位模式(这需要转换为决策或开关),则这是一种方便的技术.它也是O(1)

你可以将两者结合起来! – )

如何在C中优化这段代码的更多相关文章

  1. 使用最新的Flurry SDK和ios4重新启动应用程序

    我真的希望这对我来说只是一个愚蠢的错误.我很高兴使用Flurry但这样的事情会导致我的应用被拒绝.解决方法我写了关于这个的Flurry,他们很快回到我身边,他们会调查这个.大约一个星期后,他们回信并表示他们已经在v2.6中修复了它,现在可用了.我似乎无法重现这个问题.不是说我很棒或者什么,但我还是单枪匹马地解决了这个问题.

  2. 如何在Xcode 4.1中调试OpenCL内核?

    我有一些OpenCL内核没有做他们应该做的事情,我很想在Xcode中调试它们.这可能吗?当我在我的内核中使用printf()时,OpenCL编译器总是给我一大堆错误.解决方法将格式字符串转换为constchar*似乎可以解决此问题.这适用于Lion:这有上述错误:

  3. Swift社交应用文本输入优化汇总

    本文将汇总一下Swift社交应用文本输入优化技巧。

  4. Swift思量与初探:我需要学习Swift吗?

    最近,除了N多的基于Swift的服务端开发框架,笔者不由深思,到底该这么评价Swift呢?前两点在Swift的语法和语言特性中已经表现得淋漓尽致:像是尾随闭包,枚举关联值,可选值和强制的类型安全等都是Swift显而易见的优点。综上所述,Swift拥有着被广泛使用以及当做第一学习语言的潜质。Swift在语法层次上会更加高级,并且Swift并没有使用GC机制,因此可以与C更好地相兼容。Swift中的注释与C语言的注释非常相似。

  5. 六种语言实现输出乘法口诀表

    六种语言实现输出乘法口诀表Objective-cC语言javaJavaScriptSwiftPython可以看出不同语言又不同的写法,从上到下,代码越来越少,越来越简洁,也能够看出这些语言的各自的一些特点。

  6. Swift---一门智能型的编程语言

    Swift是苹果公司于2014年推出的一门全新的编程语言,目前已进化至第三版。简单地说,Swift是一门智能型的语言,为程序员解决了在使用很多其他的编程语言的过程中所经常遇到的问题。下面,我就拿Swift和C语言进行对比,用几个例子为大家展示Swift为何是“智能”的。从变量类型的自动推断中也可以看出,Swift具备一定的“智能”。那么,Swift是否受到了大家的欢迎呢?考虑到Swift也才推出来两年,这个排行算是不错的了。

  7. android – 使用FFmpeg检索专辑封面

    我正在开发一个依赖于FFmpeg来检索音频元数据的Android应用程序.我知道可以使用FFMpeg以编程方式检索专辑封面.但是,一旦您解码了艺术,如何生成图像文件以便在应用程序中使用?

  8. 深入剖析PHP中printf()函数格式化使用

    下面小编就为大家带来一篇深入剖析PHP中printf()函数格式化使用。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  9. 深入理解php printf() 输出格式化的字符串

    下面小编就为大家带来一篇深入理解php printf() 输出格式化的字符串。小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  10. PHP中的输出echo、print、printf、sprintf、print_r和var_dump的示例代码

    这篇文章主要介绍了PHP中的输出echo、print、printf、sprintf、print_r和var_dump的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

随机推荐

  1. 从C到C#的zlib(如何将byte []转换为流并将流转换为byte [])

    我的任务是使用zlib解压缩数据包(已接收),然后使用算法从数据中生成图片好消息是我在C中有代码,但任务是在C#中完成C我正在尝试使用zlib.NET,但所有演示都有该代码进行解压缩(C#)我的问题:我不想在解压缩后保存文件,因为我必须使用C代码中显示的算法.如何将byte[]数组转换为类似于C#zlib代码中的流来解压缩数据然后如何将流转换回字节数组?

  2. 为什么C标准使用不确定的变量未定义?

    垃圾价值存储在哪里,为什么目的?解决方法由于效率原因,C选择不将变量初始化为某些自动值.为了初始化这些数据,必须添加指令.以下是一个例子:产生:虽然这段代码:产生:你可以看到,一个完整的额外的指令用来移动1到x.这对于嵌入式系统来说至关重要.

  3. 如何使用命名管道从c调用WCF方法?

    更新:通过协议here,我无法弄清楚未知的信封记录.我在网上找不到任何例子.原版的:我有以下WCF服务我输出添加5行,所以我知道服务器是否处理了请求与否.我有一个.NET客户端,我曾经测试这一切,一切正常工作预期.现在我想为这个做一个非托管的C客户端.我想出了如何得到管道的名称,并写信给它.我从here下载了协议我可以写信给管道,但我看不懂.每当我尝试读取它,我得到一个ERROR_broKEN_P

  4. “这”是否保证指向C中的对象的开始?

    我想使用fwrite将一个对象写入顺序文件.班级就像当我将一个对象写入文件时.我正在游荡,我可以使用fwrite(this,sizeof(int),2,fo)写入前两个整数.问题是:这是否保证指向对象数据的开始,即使对象的最开始可能存在虚拟表.所以上面的操作是安全的.解决方法这提供了对象的地址,这不一定是第一个成员的地址.唯一的例外是所谓的标准布局类型.从C11标准:(9.2/20)Apointe

  5. c – 编译单元之间共享的全局const对象

    当我声明并初始化一个const对象时.两个cpp文件包含此标头.和当我构建解决方案时,没有链接错误,你会得到什么如果g_Const是一个非const基本类型!PrintInUnit1()和PrintInUnit2()表明在两个编译单元中有两个独立的“g_Const”具有不同的地址,为什么?

  6. 什么是C名称查找在这里? (&amp;GCC对吗?)

    为什么在第三个变体找到func,但是在实例化的时候,原始变体中不合格查找找不到func?解决方法一般规则是,任何不在模板定义上下文中的内容只能通过ADL来获取.换句话说,正常的不合格查找仅在模板定义上下文中执行.因为在定义中间语句时没有声明func,并且func不在与ns::type相关联的命名空间中,所以代码形式不正确.

  7. c – 在输出参数中使用auto

    有没有办法在这种情况下使用auto关键字:当然,不可能知道什么类型的.因此,解决方案应该是以某种方式将它们合并为一个句子.这可用吗?解决方法看起来您希望默认初始化给定函数期望作为参数的类型的对象.您无法使用auto执行此操作,但您可以编写一个特征来提取函数所需的类型,然后使用它来声明您的变量:然后你就像这样使用它:当然,只要你重载函数,这一切都会失败.

  8. 在C中说“推动一切浮动”的确定性方式

    鉴于我更喜欢将程序中的数字保留为int或任何内容,那么使用这些数字的浮点数等效的任意算术最方便的方法是什么?说,我有我想写通过将转换放在解析的运算符树叶中,无需将表达式转化为混乱是否可以使用C风格的宏?应该用新的类和重载操作符完成吗?解决方法这是一个非常复杂的表达.更好地给它一个名字:现在当您使用整数参数调用它时,由于参数的类型为double,因此使用常规的算术转换将参数转换为double用C11lambda……

  9. objective-c – 如何获取未知大小的NSArray的第一个X元素?

    在objectiveC中,我有一个NSArray,我们称之为NSArray*largeArray,我想要获得一个新的NSArray*smallArray,只有第一个x对象…

  10. c – Setprecision是混乱

    我只是想问一下setprecision,因为我有点困惑.这里是代码:其中x=以下:方程的左边是x的值.1.105=1.10应为1.111.115=1.11应为1.121.125=1.12应为1.131.135=1.14是正确的1.145=1.15也正确但如果x是:2.115=2.12是正确的2.125=2.12应为2.13所以为什么在一定的价值是正确的,但有时是错误的?请启发我谢谢解决方法没有理由期望使用浮点系统可以正确地表示您的帖子中的任何常量.因此,一旦将它们存储在一个双变量中,那么你所拥有的确切的一

返回
顶部