水滴石穿C语言之编译器引出的问题
基本解释
本节主要探讨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语言编译器半天不出结果,C语言之编译器引出的常见问题
基本解释 本节主要探讨C编译器下面两方面的特点所引发的一系列常见的编程问题. 对C文件进行分别编译: C程序通常由几个小程序(.c文件)组成,编译器将这几个小程序分别编译,然后通过链接程序将它们组合在 ...
- c语言编译器_学C语言写自己的K语言:编译器词法分析。
词法分析(lexical analysis),是计算机科学中将字符序列转换为记录(Token)序列化的过程.词法分析一般分手动与自动,自动是基于lex,flex词法分析器使用正则式来配置,我们这里将学 ...
- c语言菜鸟编译器,C语言菜鸟基础教程之Hello World
搜索热词 首先,需要一款C语言的编译器,可以使用在线编译器,也可以在本地安装编译器,比如Mac电脑可以安装Xcode,PC可以安装Dev C++. 编写第一个程序:Hello World! @H_50 ...
- 到哪下c语言编译器,在那里可以下载到C语言的编译器
2006-10-01 c语言的编译器是干什么用的 本人不才,仅就个人意见谈谈:1.什么是编译器:简单的说,编译器是一种将高级语言经过其解释,翻译成可以运行的二进制代码(有可能是汇编代码,但这种方式处理 ...
- php c语言在线编译器,在线C语言编译器/解释器
本文介绍两个C语言在线解释器/编译器,这些工具可以提高代码片段检测方便的工作效率,并可以保证这些代码的正确性,而且还可以和别人一起编辑/分享之间的代码,这样可以共同分析代码并相互协助完成代码段的检查. ...
- iPad能不能装c语言的编译器,IPhone/IPad/IPod安装GCC的方法
GCC是一种很常用的C语言的编译器,可以在各种版本的Windows,Linux,Mac上运行,当然IPhone的IOS系统也不例外~下面就教大家如何在IPhone上面安装GCC,帮助程序猿们实现随时随 ...
- html语言的编译器,50 行代码的 HTML 编译器
虚拟 DOM 几乎已经是现代 JS 框架的标配了.那么该怎样将 HTML 字符串编译为虚拟 DOM 呢?这样的编译器并不是什么黑科技,这里只用了不到 50 行 JS 就实现了一个. Demo 在 HT ...
- C语言 之编译器优化
C语言的编译器会对变量和代码进行一定的优化,我们看下面这个例子. int a,b,c; a=1; b=a; c=b; 这个程序正常运行的时候会这样子:先把a指向的内存空间内放入1 再把a指向的内存空间 ...
- python小项目:实现C语言在线编译器
大家好,我是牛牛. 今天给大家分享一下利用python制作C语言在线编译器的小案例,主要包含以下几部分内容: 1.项目构建与准备 2.搭建python服务器 3.前端页面与逻辑实现 4.服务端代码逻辑 ...
最新文章
- java对象 Java中 VO、 PO、DO、DTO、 BO、 QO、DAO、POJO的概念
- 解决网络故障的一般方法
- MySQL使用覆盖索引来优化limit语句
- QT的QScroller类的使用
- 使用apache模块rewrite_module
- 菜鸟教程终极篇之Microsoft Windows Pre-installation Environment (Windows PE) 2.0
- Activity的生命周期及各生命周期方法的作用
- 计算机基础:存储系统知识笔记(二)
- Ubuntu安装qwt步骤
- shell介绍,date命令,shell变量
- 无符号整型转点分十进制
- axios http请求报错: Request failed with status code 400
- 经济学家―狗屎―GDP
- Centos7.5 BCM4322无线网卡驱动安装踩坑记录
- 【战神引擎】一键打开所有修改路径快捷方式
- 机壳地与数字地_模拟地的关系
- 7-1 递归实现逆序输出整数 (15 分)
- 血族第四季/全集The Strain迅雷下载
- 步态剪影_如何拍摄好剪影照片
- Scopus使用技巧
热门文章
- 数据分析之如何制作数据埋点文档(二)
- 这些产品大咖的实战心得,学会了可以帮你跨过很多坑
- Shopee:阿里神话被打碎,东南亚第一电商平台的崛起史
- 互联网日报 | 社区团购“九不得”新规出台;小米11官宣12月28日发布;长征八号首飞成功...
- 编写程序,定义一个方法,能够判断一个1~9999之间的数是否是回文数。
- 测试两个主机之间的连通性_常用的光缆4种测试方法介绍
- 在word中怎么把文字往下挪挪_ps怎么挪动文字位置
- 【项目管理】项目管理计划
- 【Java】不使用比较运算符构造max()和min()
- 管理软件实施(3)——项目的生命周期