打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
嵌入式笔试题

一道思考题:

写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。另外,当你运行”least = MIN(*p++, b); “代码时会发生什么事?

解答: #define MIN(A,B)  ( (A) <= (B) ?  (A) : (B) )     MIN(*p++, b)会产生宏的副作用。
剖析: 这道题考察对宏定义的使用,宏定义可以实现类似于函数的功能,但是它终归不是函数,而宏定义中括弧中的“参数”也不是真的参数,在宏展开的时候对“参数”进行的是一对一的替换。             程序员对宏定义的使用要非常小心,特别要注意两个问题:
(1)谨慎地将宏定义中的“参数”和整个宏用用括弧括起来。所以,严格地讲,下
          述解答: #define MIN(A,B) (A) <= (B) ? (A) : (B)       #define MIN(A,B) (A <= B ? A : B )  都是错误的。
(2)防止宏的副作用。宏定义#define MIN(A,B) ((A) <= (B) ? (A) : (B))对MIN(*p++, b)的作用结果是: ((*p++) <= (b) ? (*p++) : (b)) 这个表达式会产生副作用,指针p会作二次++自增操作。
          除此之外,另一个典型的错误解答是: #define MIN(A,B) ((A) <= (B) ? (A) : (B)); 这个解答在宏定义的后面加“;”,显示编写者对宏的概念模糊不清。
#error预处理指令的作用是,编译程序时,只要遇到#error就会生成一个编译错误提示消息,并停止编译。
 
include   " "是先从本地目录开始寻找,然后去寻找系统路径
而Include   <>   相反先从系统目录,后从本地目录

C语言中对于表达式中存在有符号数和无符号类型时,将对计算过程进行如何处理,经常听到的说法是:

“当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型”

以上这种表述是不准确的,正确的结论应为:

“当表达式中存在有符号类型和无符号类型时,默认情况下计算的结果将转化为无符号类型”

而对于计算过程而言,变量本身转化为有符号还是无符号数,都不会改变在计算机中存储的位状态。

参考如下代码:

  1. int i,j;
  2. unsigned int x,y,z;
  3. i = -5;
    z = 2;
    x = i+z; //unsigned result
    j = i+z; //signed result
  4. printf("\033[31m  i=-5;\n  z=2;\n  x=i+z;\n  j=i+z;  \033[0m\n");
    printf("i+z=%d  \n",i+z);
    printf("x=%d  \n",x);
    printf("j=%d  \n",j);
运行结果为:
      i=-5;
      z=2;
      x=i+z;
      j=i+z;
      i+z=-3
      x=-3
      j=-3
而如果将printf()函数中的打印类型换成u%即无符号10进制类型时:
  1. int i,j;
  2. unsigned int x,y,z;
  3. i = -5;
  4. z = 2;
  5. x = i+z; //unsigned result
  6. j = i+z; //signed result
  7. printf("\033[31m  i=-5;\n  z=2;\n  x=i+z;\n  j=i+z;  \033[0m\n");
  8. printf("i+z=%u  \n",i+z);
  9. printf("x=%u  \n",x);
  10. printf("j=%u  \n",j);
运行结果为:
      i=-5;
      z=2;
      x=i+z;
      j=i+z;
      i+z=4294967293
      x=4294967293
      j=4294967293

其中,4294967293的16进制表示为0xfffffffd,即是-3的补码。
从上述结果可知,无论是无符号数变成有符号数还是有符号数变成无符号数,其计算结果都是一样的,而结果的值是取决于打印的类型。
______________________________________________________________________________________________
以下代码:
  1. int i=-5;
  2. unsigned int z=2;
  3. if(i+z>2)
  4.     printf("i+z>2  \n");
  5. else
  6.     printf("i+z<2  \n");
打印结果为:
     i+z>2

而以下代码:
  1. int i=-5;
  2. unsigned int z=2;
  3. if(((int)(i*z))>2)
  4.    printf("i+z>2  \n");
  5. else
  6.    printf("i+z<2  \n");
打印结果为:
     i+z<2

原因是在逻辑表达式判断中,i+z>2 中左侧的值需要表现为无符号类型,因此呈现为一个较大的正数。将+号改成乘号结果也一样。

因此,可信的结论应该是:

有符号数和无符号数相加时,结果的二进制表达式不受何种符号类型影响的,而计算结果的值取决于结果所需呈现的类型。

有符号数和无符号数出现在同一个表达式中,默认状态下(如果不像上述代码那样作强制转换)表达式的值为将结果转化为无符号类型的值。
 

不可重入函数不可以在它还没有返回就再次被调用。例如printf,malloc,free等都是不可重入函数。因为中断可能在任何时候发生,例如在printf执行过程中,因此不能在中断处理函数里调用printf,否则printf将会被重入。 

函数不可重入大多数是因为在函数中引用了全局变量。例如,printf会引用全局变量stdout,malloc,free会引用全局的内存分配表。

个人理解:如果中断发生的时候,当运行到printf的时候,假设发生了中断嵌套,而此时stdout资源被占用,所以第二个中断printf等待第一个中断的stdout资源释放,第一个中断等待第二个中断返回,造成了死锁,不知这样理解对不对。

不可重入函数指的是该函数在被调用还没有结束以前,再次被调用可能会产生错误。可重入函数不存在这样的问题。
不可重入函数在实现时候通常使用了全局的资源,在多线程的环境下,如果没有很好的处理数据保护互斥访问,就会发生错误。
常见的不可重入函数有:
printf --------引用全局变量stdout
malloc --------全局内存分配表
free    --------全局内存分配表
在unix里面通常都有加上_r后缀的同名可重入函数版本。如果实在没有,不妨在可预见的发生错误的地方尝试加上保护锁同步机制等等。

 

这种情况出现在多任务系统当中,在任务执行期间捕捉到信号并对其进行处理时,进程正在执行的指令序列就被信号处理程序临时中断。如果从信号处理程序返回,则继续执行进程断点处的正常指令序列,从重新恢复到断点重新执行的过程中,函数所依赖的环境没有发生改变,就说这个函数是可重入的,反之就是不可重入的。
众所周知,在进程中断期间,系统会保存和恢复进程的上下文,然而恢复的上下文仅限于返回地址,cpu寄存器等之类的少量上下文,而函数内部使用的诸如全局静态变量buffer等并不在保护之列,所以如果这些值在函数被中断期间发生了改变,那么当函数回到断点继续执行时,其结果就不可预料了。打个比方,比如malloc,将如一个进程此时正在执行malloc分配堆空间,此时程序捕捉到信号发生中断,执行信号处理程序中恰好也有一个malloc,这样就会对进程的环境造成破坏,因为malloc通常为它所分配的存储区维护一个链接表,插入执行信号处理函数时,进程可能正在对这张表进行操作,而信号处理函数的调用刚好覆盖了进程的操作,造成错误

满足下面条件之一的多数是不可重入函数
(1)使用了静态数据结构;
(2)调用了malloc或free;
(3)调用了标准I/O函数;标准io库很多实现都以不可重入的方式使用全局数据结构
(4)进行了浮点运算.许多的处理器/编译器中,浮点一般都是不可重入的 (浮点运算大多使用协处理器或者软件模拟来实现。


1) 信号处理程序A内外都调用了同一个不可重入函数B;B在执行期间被信号打断,进入A (A中调用了B),完事之后返回B被中断点继续执行,这时B函数的环境可能改变,其结果就不可预料了。
2) 多线程共享进程内部的资源,如果两个线程A,B调用同一个不可重入函数F,A线程进入F后,线程调度,切换到B,B也执行了F,那么当再次切换到线程A时,其调用F的结果也是不可预料的。
在信号处理程序中即使调用可重入函数也有问题要注意。作为一个通用的规则,当在信号处理程序中调用可重入函数时,应当在其前保存errno,并在其后恢复errno。(因为每个线程只有一个errno变量,信号处理函数可能会修改其值,要了解经常被捕捉到的信号是SIGCHLD,其信号处理程序通常要调用一种wait函数,而各种wait函数都能改变errno。)

 

写一个函数,完成内存之间的拷贝。[考虑问题是否全面,是否考虑内存重叠问题]

返回void *支持链式操作,参数类型是void *以支持任意类型的指针,输入参数加上const修饰,最好加上assert对输入输出指针进行非NULL判断

void* memcpy( void *dest, const void *src, size_t count )

{

char* pdest = static_cast<char*>( dest );

const char* psrc = static_cast<const char*>( src );

// 依次从前拷贝,目的地址覆盖了源地址的数,此时从后往前拷贝

if( (pdest>psrc) && (pdest<(psrc+count))) //能考虑到这种情况就行了

{

for( size_t i=count-1; i!=-1; --i )

pdest[i] = psrc[i];

}

else

{

for( size_t i=0; i<count; ++i )

pdest[i] = psrc[i];

}

return dest;

}
char   *ptr;

if ((ptr   =   (char   *)malloc(0))   ==   NULL)  
puts( "Got   a   null   pointer ");
else
puts( "Got   a   valid   pointer ");
上面程序在VC6.0下输出结果是:Got   a   valid   pointer
请问指针为NULL时指向哪里,分配的空间为0时又指向哪里?


当使用malloc后,只有在没有足够内存的情况下会返回NULL,或是出现异常报告。

malloc(0),系统就已经帮你准备好了堆中的使用起始地址(不会为NULL)。但是你不能对该地址进行写操作(不是不允许),如果写了话,当调用free(ptr)就会产生异常报告(地址受损)。

 

NULL   一般预定义为   (void   *)0,指向0地址。malloc是在程序堆栈上分配空间,不会是0地址

malloc(0)是指分配内存大小为零
NULL是不指向任何实体
malloc(0)也是一种存在不是NULL

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
可重入、异步信号安全和线程安全(一)
C语言难点分析整理
编程修养(二)
C语言中的函数指针与指针函数
Onvif开发之服务端发现篇
写的一个链表综合程序
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服