基本解释

本节主要探讨C编译器下面两方面的特点所引发的一系列常见的编程问题。

对C文件进行分别编译:

C程序通常由几个小程序(。c文件)组成,编译器将这几个小程序分别编译,然后通过链接程序将它们组合在一起形成一个目标代码。由于编译器每次只能编译一个文件,因此它不能立即检查需要几个源文件配合才能发现的错误。

对函数的参数和返回值建立临时变量

C编译器会对函数的参数建立临时参数,也可能会对函数的返回值隐含传递一个指针。因为这些临时变量的隐含性存在,使得在某些情况下,特别是有指针存在的时候,会引发一系列的问题。

C文件中所包含的头文件会和C语言一同编译

C语言中被包含的头文件是和。c文件一起编译的,头文件中的问题会反映到。c文件的编译中。

问题:C文件的分别编译

我有一个数组a定义在f1.c中,但是我想在f2.c中计算它的元素个数,用sizeof可以达到这个目的吗?

答案与分析:

答案是否定的,你没有办法达到目的,本质原因是sizeof操作符只是在“编译时(compile time)”起作用,而C语言的编译单位是每次单个。c文件进行编译(其它语言也都如此)。因此,sizeof可以确定同一个源文件中某个数组的大小,但是对于定义在另一个源文件中的数组它无能为力了,因为那已经是“运行时(run time)”才能确定的事情了。

一件事情要想做,总会有办法的,下面提供有三种可选的办法来解决这个问题:

1)、定义一个全局变量,让它记住数组的大小,在另外一个。c文件中我们通过访问这个全局变量来得到数组的大小信息(好像有点小题大做得不偿失^_^)。

2)、在某个。h文件中用宏定义数组的大小,例如#define ARRAY_SIZE 50,然后在两个源文件中都包含这个。h文件,通过直接访问ARRAY_SIZE来得到定义在不同。c文件中的数组的大小。

3)、设置数组的最后一个元素为特殊值,例如0,-1,NULL等,然后我们通过遍历数组来寻找这个特殊的结尾元素,从而判断数组的长度(这个办法效率低,也是笨笨的)。

问题:函数返回值隐含传递指针

下面的代码可以正常工作,但是在程序结束时会有一个致命错误产生。究竟是什么原因呢?

struct list

{

char *item;

struct list *next;

}

main (argc, argv)

{

}

答案与分析:

原因很简单,稍微注意一点不难发现,在定义结构list的右花括弧后面加一个分号就可以解决这个问题:

struct list

{

char *item;

struct list *next;

};//缺了这个分号可不行!

好了,问题是解决了,但,你知道这个错误究竟导致了什么致命问题吗?问题不是表面上那么简单的,OK,让我们来看看事情背后的真相。

首先看一看下面这段代码:

VOID Func ( struct my_struct stX)

{

…….

}

struct my_struct stY = {…};

Func (stY);

当调用函数Func的时候,是把结构变量stY的值拷贝一份到调用栈中,从而作为参数传递给函数FUNC的,这个叫做C语言的参数值传递。我相信这个你一定很清楚,那么,你应该知道:如果函数的返回值是结构变量的话,函数应该如何将值返回给调用者呢?且看下面这段代码:

struct my_structFunc (VOID)

{

…….

}

struct my_struct stY = Func();

此时函数Func的返回值是一个结构类型的值,这个返回值被放在内存中一个阴暗恐怖的地方,同时安排了一个指针指向这个地方(暂时称为“神秘指针”),而这个指针会由C语言的编译器作为一个隐藏参数传递给函数Func.当函数Func返回时,编译器生成的代码将这个由隐藏指针指向的内存区的值拷贝到返回结构stY中,从而完成将结构变量值返回给调用者。

你明白了上述所讲的东东,那么今天问题的真正原因也就呼之欲出了:

因为struct list {……}的定义后面没有加分号,导致主函数main (argc, argv)被编译器理解为是一个返回值为结构变量的函数,从而期望得到除了argc和argv以外的第三个参数,也就是我们上面提到的那个隐含传入的“神秘指针”。可是,大家知道,这里函数是main函数,main函数的参数是由程序中的启动代码(startup code)提供的。而启动代码当然认为main()天生就应该只得到两个参数,要“神秘指针”,当然没有,如此一来, main()在返回时自作主张地去调用栈中访问它的那个并不存在的第三个参数(即神秘指针),这样导致非法访问,产生致命问题。这才是这个问题的真正根源。

建议:

1)、尽量将结构变量的指针而不是结构本身作为函数参数,否则函数调用时内存拷贝的开销可不小,尤其是对那些调用频繁、结构体大的情况。

2)、结构定义的后面一定要加分号,经过上面我的大段讲述,我相信你不会犯相同的错误。

问题:编译器会给函数的参数隐含制造临时副本

请问运行下面的Test函数会有什么样的结果?

void GetMemory2(char **p, int num)

{

*p = (char *)malloc(num);

}

void Test(void)

{

char *str = NULL;

GetMemory(&str, 100);

strcpy(str, “hello”);

printf(str);

}

答案与分析:

这是林锐的《C/C++高质量编程指南》上面的例子,拿来用一下。

这样调用会产生如下两个后果:

1)、能够输出hello

2)、内存泄漏

另一个相关问题:

请问运行Test函数会有什么样的结果?

void GetMemory(char *p)

{

p = (char *)malloc(100);

}

void Test(void)

{

char *str = NULL;

GetMemory(str);

strcpy(str, “hello world”);

printf(str);

}

答案与分析:

后果严重,运行的结果是程序崩溃,通过运行调试我们可以看到,经过GetMemory后,Test函数中的 str仍旧是NULL.可想而知,一调用strcpy(str, “hello world”);程序必然崩溃了事。

原因分析:

C编译器总是会为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p.如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把_p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西,如果想要输出动态内存,请使用指向指针的指针,或者,使用指向引用的指针。

问题:头文件和包含它的。c文件一同编译,问下面的代码非常短小,看起来毫无问题,但编译器会报告一个错误,请问问题可能出现在什么地方?

#include “someheader.h”

int myint = 0;

答案与分析:

不用盯着int myint = 0;看,这一句赋值应该是C语言中最简单的语句,问题肯定不会出在它身上,那么问题只可能出现在someheader.h中,最常见的就是该头文件的最后一行的声明(函数也好,变量也好)没有用分号”;”结尾,那么编译器会将它和myint变量结合起来考虑,自然就会出错了。

这个问题主要是提醒你,在定位问题时思路要拓宽一点,可能要考虑一下所包含的头文件是否有问题。

结论:被包含的头文件是和.c文件一起编译的,头文件中的问题会反映到.c文件编译中去的,切记。

c语言编译器半天不出结果,C语言之编译器引出的常见问题相关推荐

  1. c语言单链表超市出库,c语言-单链表(二)

    继续复习链表知识点,本章包含单链表的增加,删除,判断是否为空,和链表长度,以及链表的排序 几个知识点 1.链表的判断是否为空 //1.判断链表是否为空 bool isempty_list(PNODE ...

  2. c语言执行得不出答案,C语言这个答案为什么是b?

    为了叙述方便,我把一些行加了行号,见下图: 增加了行号的程序 前面已经定义了x=0 执行第一行,是一个循环,循环变量i=0到i<2,因为里面还有一个循环,我们把这个循环叫"外循环&qu ...

  3. c语言注释符号 井号,读c语言深度剖析 -- 符号 注释符号

    标准C语言的基本符号 ,逗号  >右尖括号  . 圆点 !感叹号   :分号   | 竖线   :冒号  /斜杠   ?问号  \反斜杠 '单引号  ~波折号  "双引号  #井号   ...

  4. 注释可以出现在c语言任何位置,在c程序中,注释语句只能位于一条语句的后面吗...

    错误,在C语言中注释部分对程序的运行结果不产生任何影响,它可以出现在程序的任何位置.在C语言中有两种注释方式:一种是以"/*"开始.以"*/"结束的块注释:一种 ...

  5. f77编程和c语言的区别,在fortran中l用F77编译器编译程序时出现问题?

    高级语言.汇编语言.机器语言这三种语言统称"计算机语言".其中,机器语言是最低级的,是一串一串的电流表示"0001101010010110010101000110&quo ...

  6. 二、半天时间掌握Dart开发语言-类的学习

    在学习本篇内容之前请先学习一.半天时间掌握Dart开发语言-基础学习_WEB前端李志杰的博客-CSDN博客 掌握Dart开发语言的基础. Dart 是面向对象的编程语言,Dart中类的很多概念跟其他语 ...

  7. Java语言的介绍,Java环境的配置以及Java编译器的安装

    Java语言的介绍,Java环境的配置以及Java编译器的安装 文章目录 Java语言的介绍,Java环境的配置以及Java编译器的安装 Java 简介 主要特性 关于语言的选择 Python Jav ...

  8. 编译器-FORTRAN(公式翻译)语言,第一个全球正式推广的高级语言,Pascal(帕斯卡)可称为第一个结构化程序设计语言Pascal基于ALGOL编程语言BASIC初学者通用符号指令代码17语句微软

    编译器-FORTRAN(公式翻译)语言,第一个全球正式推广的高级语言,Pascal(帕斯卡)它可称为第一个结构化程序设计语言,Pascal基于ALGOL编程语言,ALGOL是算法语言(ALGOrith ...

  9. 用c语言编写一个简易的编译器,面向教学的简易c语言编译器的设计与实现(54页)-原创力文档...

    目录 TOC \o "1-5" \h \z \o "Current Document" 摘要I ABSTRACTII \o "Current Docu ...

最新文章

  1. React 组件绑定点击事件,并且传参完整Demo
  2. javascript函数、对象及变量、正则表达式(7.19)
  3. OVS datapath之action分析(十九)
  4. Xcode 8.0 doc 注释的问题
  5. MIGO+103收货到GR冻结库存和MB1B+344从非限制到冻结区别?
  6. .bat文件该图标_电脑桌面图标变成白色方块图标怎么办?
  7. Linux下Jenkins与GitHub自动构建NetCore与部署
  8. 还有更多REST技巧
  9. 《Python Cookbook 3rd》笔记(2.12):审查清理文本字符串
  10. linux启动脚本添加命令,Linux添加shell(.sh)脚本并添加定时任务
  11. 推荐系统遇上深度学习(八)--AFM模型理论和实践
  12. 小明历险记:规则引擎drools教程一
  13. shell下利用运算方式编写倒计时脚本
  14. 内存颗粒和闪存颗粒的区别_国产闪存颗粒终于熬出头 紫光存储S100固态硬盘评测...
  15. vs2005下载,中文版,官方
  16. Lingoes 一款功能强大、简明易用的多语言词典和文本翻译软件
  17. matlab 向量转置,matlab中向量和矩阵怎么转置 值得收藏
  18. PHP多国语言开发:CodeIgniter 2PHP框架中的多国语言,语言包(i18n)库
  19. Android云手机平台搭建-2020圈钱热潮
  20. meta http-equiv 属性 详解

热门文章

  1. 培育下属就象雕琢宝玉
  2. MySQL之存储引擎及SQL优化
  3. zz学习富豪理财精粹 让有限的钱无限花!(1)
  4. 对工具的探索 - 对工具、工作流、自动化的杂思
  5. 数据挖掘技术-检测与处理缺失值
  6. Problem G: 点歌单
  7. 想让JS弹出窗口显示在第二个(辅助)显示器上?
  8. 基于Swing的Java扫雷游戏
  9. 功能测试Ⅵ——后台会员列表
  10. 钱诚1.5黄金原油全面操作建议,黄金原油小非农方向布局