几条C Puzzle Problem中的为什么

  发现了一个收集C puzzle的页面,挺有意思的,都看了一遍。除去那些常见的Marco、类型转换、printf格式符、指针和内存之类的常见puzzle外,我发现以下一个puzzle很是值得想一想。

问题1

The following program doesn't "seem" to print "hello-out". (Try executing it)

  #include 
<stdio.h>
  #include 
<unistd.h>
  
int main()
  
{
          
while(1)
          
{
                  fprintf(stdout,
"hello-out");
                  fprintf(stderr,
"hello-err");
                  sleep(
1);
          }

          
return 0;
  }


What could be the reason
?

    该程序的执行结果有些怪异:在终端上只看到“hello-err"一个接一个的输出,过了一段时间后突然输出一连串的“hello-out",然后又是只输出“hello-err“,如是反复。

    这里需要了解的是ISO C中对于流的缓冲的相关规定。ISO C为标准I/O提供了三种类型的缓冲:

    全缓冲:缓冲区填满后才执行实际I/O操作
    行缓冲:在输入和输出中遇到换行符是,执行I/O操作
    不带缓冲:不启用缓冲机制

    而对于stdout,stdin和stderr这三个默认提供的流,ISO C 有如下要求

    (1). 当且仅当stdout和stdin不涉及交互式设备时,才允许是全缓冲的
    (2). stderr决不会是全缓冲的(通常是不缓冲的)

    OK,现在这个程序的怪异表现就很容易解释了。

问题2

The following C program segfaults of IA-64, but works fine on IA-32.

  
int main()
  
{
      
int* p;
      p 
= (int*)malloc(sizeof(int));
      
*= 10;
      
return 0;
  }


Why does it happen so
?

    这个问题很好。估计多数人最初都往野指针或是类型错误上面想,可是再仔细看看又找不出漏洞。究竟问题处在何处?

    问题在于:应该加上“#include <stdlib.h> ",这才是符合标准的调用malloc的必要前提。

    在目前的ISO C标准中,仍然延续了从K&R C继承而来的”默认int类型“的特性。在未提供function prototype的情况下,编译器会假设malloc的参数和返回类型都是int。

    在
IA-32 机器上,因为int和 void *都是32bits ,因此不会出现问题。而在 IA-64机器上,默认的内存模型是LP-64,即int为32bits而long和void *都为64bits。因此malloc的返回值在缺少prototype的时候被截断(truncated)为32字节,自然会出现segment error

    教训:无论何时,都要保证在调用函数前已经正确声明其prototype。

问题3
What ' s the output of the following program and why?

  #include 
< stdio.h >
  
int  main()
  
{
   
float a = 12.5;
   printf(
"%d/n ", a);
   printf(
"%d/n " *(int *)&a);
   
return 0;
  }


程序的运行结果是

0
1095237632 


这个问题的实质在于了解在调用诸如类似printf这样具有可变参数的函数时,编译器会隐式进行类型提升——所有整数类型(int ,short)都被提升为int,所有浮点类型都被提升为double,再将参数压栈传给printf。

因此对于
“printf("%d ", a);”这条调用,float a会被提升为double。通过debugger我们可以看到a的内存布局为:

0x00    0x00    0x48    0x41,

而提升后对应的(临时)double变量的内存布局为

0x00    0x00    0x00    0x00    0x00    0x00    0x29    0x40


这样问题就清楚了,printf根据格式说明符%d从栈中提取一个int类型大小的参数,那么得到的就是0了


问题4
 Note : This question has more to  do  with Linker than C language
We have three files a.c, b.c and main.c respectively 
as  follows:
a.c
---

int  a;

b.c
---

int  a  =   10 ;

main.c
------

extern   int  a;
int  main()
{
        printf(
"a = %d ",a);
        
return 0;
}


Let
' s see what happens, when the files are compiled together:

bash$ gcc a.c b.c main.c
bash$ .
/ a. out
=   10

Hmm
!!  no compilation / linker error !!!  Why  is  it so ??

    这个问题严格的说与C什么关系,关键在于linker的符号解析过程。

    编译时,编译器将每个全局符号都输出给汇编器,其中函数和已初始化的变量为强符号,未初始化的符号为若符号;汇编器再将这些信息编码在可重定位目标文件的符号表中。

    根据强弱符号的定义,unix链接其根据如下规则来处理多出定义的符号:
    (1).不允许有多个强符号。
    (2).如果有一个强符号的多个弱符号,选择强符号
    (3).如果没有强符号而有多个弱符号,则从这些弱符号中任意选一个

    该例子遵循了了上述规则的中的第一条




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值