`

初级程序员面试不靠谱指南(四)

阅读更多
原帖地址:http://www.cnblogs.com/ZXYloveFR/archive/2013/05/29/3106998.html

三、改变出生的static和extern


1.程序是怎样炼成的?IDE的发展带来的一个好处就是在写程序更多的可以关注在写这件事情上了,比如在vc里面,当你酣畅淋漓的写完一段程序之后,点击一下debug就可以享受到程序运行起来的好感,但是IDE带来的一个负面影响就是对ASCII码写成的代码如何转换成二进制的计算机程序掩盖的越来越多,虽然说不懂这些在绝大多数情况下对写程序也没什么影响,但是面试的时候如果你能有这方面的知识那绝对是加很多分的,而且现在来说很多时候面试这也成了一个基础的内容,特别是面试linux下开发的时候。先用一副图说明下点击debug/run以后都大概干了什么,我找了很多图,确实难找,这玩意儿,最后找了一个差不多的但是是linux下的过程的先用用:



     这个图中看起来步骤也挺多的,如果实在是记不住这么多的过程,你可以忽略源文件、预处理、纯C、编译器、汇编器这些玩意儿,就记住源文件在编译以后会先转换成为.o(windows下是.obj),这些被称为目标文件,其实就是一个个的编译单元,也就是在(一)里面提到的translation unit,然后这些编译单元和一些库文件(windows下是.lib/.dll)进行连接产生可执行文件。


     这个整个过程可以这样理解,某位领导需要在某个大会上发言,他自己首先要写一些自己想发言的关键点,然后交给秘书写成完整可用的发言稿,领导是高级职位(高级语言),他可以很简略的列出一些要点和开会需要的文件,然后交给秘书,秘书首先要看懂这些简略的要点并寻找这些文件(预处理),在这些都收集妥了的时候,秘书开始根据这些东西写成正式的发言稿,首先他要将领导列出的要点一个个补充完整(汇编语言,汇编器),尽量让语言更通俗易懂,并按照单独整理出每个要点(目标文件),然后再去寻找需要参考的文件(库文件),将这些要点和(库文件)相结合,整理写成可以用来发言的正式的发言稿(可执行文件)。


    说了这么多,展示下vc产生的obj文件更直观一点,我建了一个纯c的工程(cpp,c这个无所谓,为了切入更关键的部分,采用c展示),在随意写一些测试内容之后,点击debug,转到相应的文件夹,可以看到.obj文件。


   


     为了初窥下obj里面到底有啥,我们采用记事本打开这个obj文件看看,可以看到内容如下:


          虽然不能完全懂这里面有啥,但是可以大概从这些东西看到都是一些编译信息啥的,比如里面的_main,这就是main函数,这一次不是要叙述IDE的研究,这些只是为了后面说明static和extern之间做一些准备工作。


2.声明与定义声明和定义在英语中有点接近(declaration和definition),而且这个两个词又经常一起出现,搞得我最开始看英文书籍的时候到后面就怀疑自己哪个是哪个了,这两个概念在程序之中有巨大的区别,也是很多面试的时候会涉及的地方。首先得弄明白啥是声明,啥是定义。从编译原理上来说,声明是仅仅告诉编译器,有个某类型的变量会被使用,但是编译器并不会为它分配任何内存。而定义就是分配了内存。有些书本里会说,int a;这是声明,int b=1;这是定义,但是如果你去main里面试试发现并不是这样的,因为a和b都被分配了内存。在这里要说的是有的书里面把第一个称为定义性声明,它是会被分配内存并且有一个随机分配的垃圾值,而把真正的纯声明叫做引用性声明,必须用extern。所以有的时候,为了叙述方便,把建立存储空间的声明称为定义,而把不需要建立存储空间的声明称为声明。显然这里指的声明是狭义的,即非定义性声明。最终归结下来的核心问题就是声明是不会分配内存的,而定义需要,还有一个需要记住的就是对同一个变量或函数的声明可以有多次,而定义只能有一次!


    声明和定义和连接是分不开,有时候程序出现的连接错误就是和声明与定义有关系,这个后面具体进行描述。


3.由内而外的extern。extern可以改变变量的连接属性,可以将一个变量的连接属性由internal改变成为external,也就是说由extern标示的变量不仅可以在本模块中被使用,在该工程的所有模块中都可以被使用,不会出现找不到的错误。而且用extern标示的一定是一个声明,没有定义。下面结合前面的.obj文件来对这句话进行解释。


   在上面的mainfile.h中添加一个全局的变量如下:



#ifndef MAINFILE_H
#define MAINFILE_H

extern char *roger_str; //仅仅是做了声明,没有定义。

#endif


   然后在mainfile中包含这个头文件并且给这个字符串赋值(这时才进行了定义),大概类似如下所示,这里我们不需要main函数,只是为了掩饰连接属性。编译一下。



#include "MainFile.h"

char* roger_str="Roger Zhu";//定义

void MainFileFun()
{
    printf("%s\r\n",roger_str);
}


    找到debug文件夹里面相应的mainfile.obj文件,用记事本打开,可以看到里面有Roger Zhu,roger_str之类的内容。



    下面我们再在这个工程内添加一个新的文件,叫做LinkFile.c,在这个文件中,我们仅仅引用MainFile.h,但同时也使用roger_str,但我们没有再次定义这个变量,编译一下能够通过,运行也不会报连接错误,只是会说找不到main,因为我们根本没有定义main。其内容如下所示:



#include "MainFile.h"
#include <stdio.h>

void LinkFileFun()
{
printf("%s\r\n",roger_str);
}


    然后找到LinkFile.obj,查看一下其内容,发现其中有roger_str的内容,但是没有Roger Zhu字符串,但是在该文件中声明一个main函数再调用LinkFileFun函数,会发现,输出的是正确的Roger Zhu。在obj文件中只有一份的原因是因为这是一个全局变量,在内存中只能有一个。


  


    如果你在LinkFile中同样也定义一次全局变量roger_str会发生什么呢?程序可以通过编译,但是会曝出类似如下的连接错误: LinkFile.obj : error LNK2005: _roger_str already defined in MainFile.obj。这也和第二点里面的对同一个变量可以声明多次但是只能定义一次的阐述进行了验证。


    还有需要注意一点的是,如果使用的是extern char *roger_str=“Roger Zhu”;这相当于没有使用过extern,所以不小心也会造成连接错误,关于这一点你可以自己试一下。



4.由外而内的static。static也是一个经常被考的内容,最常见的一个答案是static变量时在栈上进行分配的,它具有全局作用域(其实我觉得这个说法不恰当,应该是模块作用域或者文件作用域比较好),可以作为一个全局计数器,其原因是因为static是在全局数据区进行分配的,所以其可以保持这样的特性。比如像下面这样:



int LinkFileFun2()
{
static int i=0;
i++;
return i;
}

void main()
{
int count=0;
int k=0;
for(;k<10;k++) count=LinkFileFun2();
printf("%d\r\n",count);
}


     static另外的一个作用就是将连接属性从external改变成internal,static声明的变量只能在自己的编译单元中是可见的,同样采用3里面的例子,不同的是将头文件中的extern改变成为static,可以看到同样可以通过编译和运行,但是,如果在LinkFile.c中调用LinkFileFunc会发现,输出的字符串是null,也就是说该编译单元并不能找到另外一个编译单元的roger_str,为了显示在.obj文件中的不同,我们在LinkFileFunc中也定义一个roger_str为Roger Zhu 2,进行编译,运行,不会再报连接错误而且可以看到输出的是Roger Zhu 2,mainfile.obj并没有什么不同,但是linkfile.obj会多出Roger Zhu 2这样的字符串,这是因为static具有模块作用域,它不和其余的模块发生关系,所包含的变量也仅仅是自己模块中可见。如果你要是在MainFile中输出roger_str,会发现输出结果是Roger Zhu,也再一次证明了作用域是模块的。


5.extern的static变量的位置。是不是extern和static变量只能在头文件中声明而在源文件中定义呢?这个看起来不经意的问题其实也蕴含了很多的知识点的,比如说extern标记的变量,我们在头文件中对roger_str进行定义而不是声明,也就是用在头文件全局的地方使用 char *roger_str=“Roger Zhu”;注释掉MainFile.c中的对roger_str的定义。进行编译,运行,这是会出现连接错误LinkFile.obj : error LNK2005: _roger_str already defined in MainFile.obj,因为你把全局变量roger_str在头文件中进行定义了以后 ,MainFile.c这个模块包含了头文件,根据预编译的原理,头文件会在这里被展开,于是定义了一次roger_str,同理LinkFile.c又包含了一次头文件,又定义了一次roger_str,这和2里面所描述的问题是相同的。如果想要改正这个错误,只有在LinkFile.c不要包含.h文件,那么编译器怎么知道这个roger_str是怎么来的呢?如果什么也不加,很明显只能在本模块中可见,这时要把internal连接属性,转换为external的,就需要使用extern,也就是需要在LinkFile.c的全局处声明extern char *roger_str,这时再编译程序回去其他编译单元寻找这个变量,编译连接都可以通过。但这里出现了一个问题,如果不能包含头文件意味着头文件里面的所有内容想使用的话都需要用extern进行标记,这个对于编程和维护都是极为不便的,所以,带有extern的变量应该在头文件中声明,在源文件中进行定义。这样就可以避免上述问题。


     关于static,尝试跟踪调试上面的代码,结果你发现在两个源文件中的roger_str的内存地址是相同的,这难道说明这是一个全局的作用变量?因为如果地址是相同的,那么无论你怎么修改,肯定修改的是一个变量,但是这和static本身的意义不一样啊?其实这是编译器为了优化做的一个调整,当它发现在两个模块中,对于两个static变量并没有做写的操作,那么它会将其安排在一个内存,这样更节省内存。想证明这个很简单,只要在其中的一个模块static变量进行调整,这时候再跟踪内存会发现两个的内存地址并不相同。所以在使用static的时候,为了避免造成这样的干扰,一般放在源文件中,这样可以避免给其他模块的static全局变量造成不必要的干扰。


     还有一些是关于类中使用extern和static的内容,我将在后面详述,还有就是和const的结合,已经在(一)中进行了叙述,这里就不累述了。

本文链接

分享到:
评论

相关推荐

    初级程序员面试题

    这份"初级程序员面试题"旨在考察候选人的基本素质,确保他们具备入门级编程所需的技能和理解力。下面,我们将深入探讨这些面试题可能涵盖的知识点,帮助准备面试的候选人了解可能面对的挑战。 1. **基础编程概念** ...

    程序员面试宝典第四版.zip

    程序员面试宝典第四版 程序员面试宝典第四版.zip

    java初级程序员面试题 java初级工程师面试题

    java初级程序员面试题 java初级工程师面试题

    .NET程序员面试指南2

    .NET程序员面试指南2 .NET程序员面试指南2 .NET程序员面试指南2

    程序员面试宝典(高清第四版)

    程序员面试宝典高清第四版PDF,需要面试的小伙伴可以看看,帮助很大 程序员面试宝典高清第四版PDF,需要面试的小伙伴可以看看,帮助很大 程序员面试宝典高清第四版PDF,需要面试的小伙伴可以看看,帮助很大 程序员...

    .net程序员面试指南

    《.NET程序员面试指南》是一本专为准备.NET程序员面试者设计的实用参考资料,旨在帮助求职者更好地理解和应对面试中的各种技术问题。该指南涵盖了.NET框架的基础知识、C#编程语言、ASP.NET web开发、数据库交互、...

    java程序员面试交流项目经验

    java程序员面试交流项目经验java程序员面试交流项目经验java程序员面试交流项目经验java程序员面试交流项目经验java程序员面试交流项目经验java程序员面试交流项目经验java程序员面试交流项目经验java程序员面试交流...

    C++程序员面试技巧.pdf

    C++程序员面试技巧.pdf,C++程序员面试技巧.pdf

    c#初级程序员面试题 c#初级工程师面试题

    c#初级程序员面试题 c#初级工程师面试题

    .NET程序员面试指南1.pdf

    这是.NET程序员面试指南1 .NET程序员面试指南2=http://download.csdn.net/source/1597090

    程序员面试全攻略

    《程序员面试全攻略》是一本全面指导程序员求职过程的实用指南,旨在帮助程序员们在竞争激烈的IT行业中脱颖而出,成功找到理想的工作。这本书涵盖了从准备简历、与猎头接触,到面试技巧和程序设计能力的提升等多个...

    前端初级程序员面试宝典

    【前端初级程序员面试宝典】是一本专门为正在寻求前端开发职位的初级工程师和学生准备的面试指南。它涵盖了从基础到进阶的各种知识点,旨在帮助求职者全面了解和掌握前端开发的核心技能,以便在面试中表现出色。 ...

    一本关于程序员面试的书籍,希望大家喜欢

    除了技术知识,这本书还可能包含面试技巧、简历编写指南、职场沟通策略等内容,帮助程序员全方位提升面试表现。无论你是初入职场的新手,还是寻求职业发展的资深开发者,《程序员面试宝典》都是一本值得阅读和参考的...

    程序员面试指南(英文版)

    《程序员面试指南(英文版)》是一本专为程序员准备的面试宝典,由John Mongan、Noah Suojanen和Eric Gigure三位作者共同撰写。这本书详细讲解了程序员在求职过程中可能遇到的各种面试问题,涵盖了从基础的编程概念...

    软考初级程序员真题

    《软考初级程序员真题解析》 软考初级程序员真题是针对计算机软件技术资格考试初级程序员级别的考生的重要参考资料,涵盖了从2007年至2011年历年的真实考试题目。这些真题集不仅是检验考生理论知识和编程技能的重要...

    Java程序员面试资料及简历模版

    Java程序员面试资料及简历模版 Java程序员面试资料及简历模版 Java程序员面试资料及简历模版 Java程序员面试资料及简历模版 Java程序员面试资料及简历模版 Java程序员面试资料及简历模版 Java程序员面试资料及简历...

    程序员面试笔试宝典

    《程序员面试笔试宝典》是一本专为Java程序员面试准备的综合指南,涵盖了从基础知识到实战应用的广泛内容。这份PDF文档是书籍的PAD版本,提供了高清的阅读体验,特别适合那些正在为Java面试笔试做准备的开发者们。...

    程序员代码面试指南-代码

    程序员代码面试指南:IT名企算法与数据结构题目最优解左程云 著 这是一本程序员面试宝典!书中对IT名企代码面试各类题目的最优解进行了总结,并提供了相关代码实现。针对当前程序员面试缺乏权威题目汇总这一痛点,...

    java程序员面试指南(代码

    Java程序员面试指南旨在帮助求职者准备Java开发职位的面试,涵盖了广泛的编程概念和技术知识点。这份指南可能包括了从基础语法到高级特性的深入讨论,以及实际编程问题的解决方案。以下是一些可能涵盖的重要知识点:...

    JAVA程序员面试指南

     第二篇(第2章~第13章)介绍了Java程序员涉及的基础知识,内容包括Java语言基础、异常的处理、I/O控制流、面向对象编程、线程、集合以及数据库技术等基本知识点。  第三篇(第14章~第16章)专门介绍了Java开发...

Global site tag (gtag.js) - Google Analytics