现在的位置: 首页 > 综合 > 正文

Deep C (and C++) by Olve Maudal and Jon Jagger— 很不错的国外技术文章

2013年02月08日 ⁄ 综合 ⁄ 共 6110字 ⁄ 字号 评论关闭

编程是困难的,正确的使用C/C++编程尤其困难。确实,不管是C还是C++,很难看到那种良好定义并且编写规范的代码。为什么专业的程序员写出这样的代码?因为绝大部分程序员都没有深刻的理解他们所使用的语言。他们对语言的把握,有时他们知道某些东西未定义或未指定,但经常不知道为何如此。这个幻灯片,我们将研究一些小的C/C++代码片段,使用这些代码片段,我们将讨论这些伟大而充满危险的语言的基本原则,局限性,以及设计哲学。

 

        
假设你将要为你的公司招聘一名C程序言,你们公司是做嵌入式开发的,为此你要面试一些候选人。作为面试的一部分,你希望通过面试知道候选人对于C语言是否有足够深入的认识,你可以这样开始你们的谈话:

[cpp]view
plain
copyprint?

1.  
int main ()  

2.  
{  

3.  
         int a= 42;  

4.  
         printf(“%d\n”,a);  

5.  
}  

int main ()

{

        
int a= 42;

        
printf(“%d\n”,a);

}

 

当你尝试去编译链接运行这段代码时候,会发生什么?

 

一个候选者可能会这样回答:

        
你必须通过#include<stdio.h>包含头文件,在程序的后面加上 return 0;然后编译链接,运行以后将在屏幕上打印42.

        
没错,这个答案非常正确。

 

        
但是另一个候选者也许会抓住机会,借此展示他对C语言有更深入的认识,他会这样回答:

        
你可能需要#include<stdio.h>,这个头文件显示地定义了函数printf(),这个程序经过编译链接运行,会在标准输出上输出42,并且紧接着新的一行。

        
然后他进一步说明:

         C++编译器将会拒绝这段代码,因为C++要求必须显示定义所有的函数。然而,有一些特别的C编译器会为printf()函数创建隐式定义,把这个文件编译成目标文件。再跟标准库链接的时候,它将寻找printf()函数的定义,以此来匹配隐式的定义

        
因此,上面这段代码也会正常编译、链接然后运行,当然你可能会得到一些警告信息。

 

        
这位候选者乘胜追击,可能还会往下说,如果是C99,返回值被定义为给运行环境指示是否运行成功,正如C++98一样。但是对于老版本的C语言,比如说ANSI
C
以及K&R C,程序中的返回值将会是一些未定义的垃圾值。但是返回值通常会使用寄存器来传递,如果返回值的3,我一点都不感到惊讶,因为printf()函数的返回值是3,也就是输出到标准输出的字符个数

        
说到C标准,如果你要表明你关心C语言,你应该使用 intmain (void)作为你的程序入口,因为标准就这么说的。

         C语言中,使用void来指示函数声明中不需要参数。如果这样声明函数int f(),那表明f()函数可以有任意多的参数,虽然你可能打算说明函数不需要参数,但这里并非你意。如果你的意思是函数不需要参数,显式的使用void,并没有什么坏处。

[cpp]view plaincopyprint?

1.  
int main (void)  

2.  
{  

3.  
         inta = 42;  

4.  
         printf(“%d\n”,a);  

5.  
}  

int main (void)

{

        
inta = 42;

        
printf(“%d\n”,a);

}

 

然后,有点炫耀的意思,这位候选人接着往下说:

        
如果你允许我有点点书生气,那么,这个程序也并不完全的符合C标准,因为C标准指出源代码必须要以新的一行结束。像这样:

[cpp]view plaincopyprint?

1.  
int main ()  

2.  
{  

3.  
         inta = 42;  

4.  
         printf(“%d\n”,a);  

5.  
}  

6.  
   

int main ()

{

        
inta = 42;

        
printf(“%d\n”,a);

}

 

 

同时别忘了显式的声明函数printf()

[cpp]view plaincopyprint?

1.  
#include <stdio.h>   

2.  
int main (void)  

3.  
{  

4.  
         inta = 42;  

5.  
         printf(“%d\n”,a);  

6.  
}  

7.  
   

#include <stdio.h>

int main (void)

{

         inta = 42;

        
printf(“%d\n”,a);

}

 

 

现在看起来有点像C程序了,对吗?

 

然后,在我的机器上编译、链接并运行此程序:

[plain]view plaincopyprint?

1.  
$  cc–std=c89 –c foo.c  

2.  
$  ccfoo.o  

3.  
$ ./a.out  

4.  
42  

5.  $ echo $?  

6.  3  

7.  
   

8.  
   

9.  
$  cc–std=c99 –c foo.c  

10.   
$  ccfoo.o  

11.   
$ ./a.out  

12.   
42  

13.   $ echo $?  

14.   0  

cc–std=c89 –c foo.c

ccfoo.o

$ ./a.out

42

$ echo $?

3

 

 

cc–std=c99 –c foo.c

ccfoo.o

$ ./a.out

42

$ echo $?

0

这两名候选者有什么区别吗?是的,没有什么特别大的区别,但是你明显对第二个候选者的答案更满意

 

       
也许这并不是真的候选者,或许就是你的员工,呵呵。

        
让你的员工深入理解他们所使用的语言,对你的公司会有很大帮助吗?

        
让我们看看他们对于C/C++理解的有多深……

        

[cpp]view plaincopyprint?

1.  
#include <stdio.h>   

2.  
   

3.  
void foo(void)  

4.  
{  

5.  
   int a = 3;  

6.  
   ++a;  

7.  
   printf("%d\n", a);  

8.  
}  

9.  
   

10.   
int main(void)  

11.   
{  

12.   
   foo();  

13.   
   foo();  

14.   
   foo();  

15.   
}  

#include <stdio.h>

 

void foo(void)

{

   int a = 3;

   ++a;

   printf("%d\n", a);

}

 

int main(void)

{

   foo();

   foo();

   foo();

}


 

这两位候选者都会是,输出三个4.然后看这段程序:

[cpp]view plaincopyprint?

1.  
#include <stdio.h>   

2.  
   

3.  
void foo(void)  

4.  
{  

5.  
   static int a = 3;  

6.  
   ++a;  

7.  
   printf("%d\n", a);  

8.  
}  

9.  
   

10.   
int main(void)  

11.   
{  

12.   
   foo();  

13.   
   foo();  

14.   
   foo();  

15.   
}  

16.   
   

#include <stdio.h>

 

void foo(void)

{

   static int a = 3;

   ++a;

   printf("%d\n", a);

}

 

int main(void)

{

   foo();

   foo();

   foo();

}

 

 

他们会说出,输出4,5,6.再看:

[cpp]view plaincopyprint?

1.  
#include <stdio.h>   

2.  
   

3.  
void foo(void)  

4.  
{  

5.  
   static int a;  

6.  
   ++a;  

7.  
   printf("%d\n", a);  

8.  
}  

9.  
   

10.   
int main(void)  

11.   
{  

12.   
   foo();  

13.   
   foo();  

14.   
   foo();  

15.   
}  

16.   
   

#include <stdio.h>

 

void foo(void)

{

   static int a;

   ++a;

   printf("%d\n", a);

}

 

int main(void)

{

   foo();

   foo();

   foo();

}

 

 

第一个候选者发出疑问,a未定义,你会得到一些垃圾值?

你说:不,会输出1,2,3.

候选者:为什么?

你:因为静态变量会被初始化未0.

 

第二个候选者会这样来回答:

         C标准说明,静态变量会被初始化为0,所以会输出1,2,3.

 

再看下面的代码片段:

[cpp]view plaincopyprint?

1.  
#include <stdio.h>   

2.  
   

3.  
void foo(void)  

4.  
{  

5.  
   int a;  

6.  
   ++a;  

7.  
   printf("%d\n", a);  

8.  
}  

9.  
   

10.   
int main(void)  

11.   
{  

12.   
   foo();  

13.   
   foo();  

14.   
   foo();  

15.   
}  

#include <stdio.h>

 

void foo(void)

{

   int a;

   ++a;

   printf("%d\n", a);

}

 

int main(void)

{

   foo();

   foo();

   foo();

}

 

第一个候选者:你会得到1,1,1.

你:为什么你会这样想?

候选者:因为你说他会初始化为0.

你:但这不是静态变量。

候选者:哦,那你会得到垃圾值。

 

第二个候选者登场了,他会这样回答:

a的值没有定义,理论上你会得到三个垃圾值。但是实践中,因为自动变量一般都会在运行栈中分配,三次调用foo函数的时候,a有可能存在同一内存空间,因此你会得到三个连续的值,如果你没有进行任何编译优化的话。

你:在我的机器上,我确实得到了1,2,3.

候选者:这一点都不奇怪。如果你运行于debug模式,运行时机制会把你的栈空间全部初始化为0.

 

 

接下来的问题,为什么静态变量会被初始化为0,而自动变量却不会被初始化?

第一个候选者显然没有考虑过这个问题。

第二个候选者这样回答:

把自动变量初始化为0的代价,将会增加函数调用的代价。C语言非常注重运行速度。

然而,把全局变量区初始化为0,仅仅在程序启动时候产生成本。这也许是这个问题的主要原因

更精确的说,C++并不把静态变量初始化为0,他们有自己的默认值,对于原生类型(native
types
)来说,这意味着0

 

再来看一段代码:

[cpp]view plaincopyprint?

1.  
#include<stdio.h>   

2.  
   

3.  
static int a;  

4.  
   

5.  
void foo(void)  

6.  
{  

7.  
    ++a;  

8.  
    printf("%d\n", a);  

9.  
}  

10.   
   

11.   
int main(void)  

12.   
{  

13.   
    foo();  

14.   
    foo();  

15.   
    foo();  

16.   
}  

17.   
   

#include<stdio.h>

 

static int a;

 

void foo(void)

{

    ++a;

    printf("%d\n", a);

}

 

int main(void)

{

    foo();

    foo();

    foo();

}

 

 

第一个候选者:输出1,2,3.

你:好,为什么?

候选者:因为a是静态变量,会被初始化为0.

你:我同意……

候选者:cool…

 

 

这段代码呢:

[cpp]view plaincopyprint?

1.  
#include<stdio.h>   

2.  
   

3.  
int a;  

4.  
   

5.  
void foo(void)  

6.  
{  

7.  
    ++a;  

8.  
    printf("%d\n", a);  

9.  
}  

10.   
   

11.   
int main(void)  

12.   
{  

13.   
    foo();  

14.   
    foo();  

15.   
    foo();  

16.   
}  

17.   
   

#include<stdio.h>

 

int a;

 

void foo(void)

{

    ++a;

    printf("%d\n", a);

}

 

int main(void)

{

    foo();

    foo();

    foo();

}

 

 

第一个候选者:垃圾,垃圾,垃圾。

你:你为什么这么想?

候选者:难道它还会被初始化为0

你:是的。

候选者:那他可能输出1,2,3

你:是的。你知道这段代码跟前面那段代码的区别吗?static那一段。

候选者:不太确定。等等,他们的区别在于私有变量(private variables)和公有变量(public variables).

你:恩,差不多。

 

第二个候选者:它将打印1,2,3.变量还是静态分配,并且被初始化为0.和前面的区别:嗯。这和链接器(linker)有关。这里的变量可以被其他的编译单元访问,也就是说,链接器可以让其他的目标文件访问这个变量。但是如果加了static,那么这个变量就变成该编译单元的局部变量了,其他编译单元不可以通过链接器访问到该变量。

        
你:不错。接下来,将展示一些很不错的玩意。静候:)

好,接着深入理解C/C++之旅。我在翻译第一篇的时候,自己是学到不不少东西,因此打算将这整个ppt翻译完毕。

 

请看下面的代码片段:

[cpp]view plaincopyprint?

1.  
#include <stdio.h>   

2.  
  

3.  
void foo(void)  

4.  
{  

5.  
    int a;  

6.  
    printf("%d\n", a);  

7.  
}  

8.  
  

9.  
void bar(void)  

10.   
{  

11.   
    int a = 42;  

12.   
}  

13.   
  

14.   
int main(void)  

15.   
{  

16.   
    bar();  

17.   
    foo();  

18.   
}  

抱歉!评论已关闭.