万恶之源:C语言中的隐式函数声明
1 什么是C语言的隐式函数声明
在C语言中,函数在调用前不一定非要声明。如果没有声明,那么编译器会自己主动依照一种隐式声明的规则,为调用函数的C代码产生汇编代码。以下是一个样例:
int main(int argc, char** argv)
{
double x = any_name_function();
return 0;
}
单纯的编译上述源代码。并没有不论什么报错,仅仅是在链接阶段由于找不到名为any_name_function的函数体而报错。
[smstong@centos192 test]$ gcc -c main.c
[smstong@centos192 test]$ gcc main.o
main.o: In function `main':
main.c:(.text+0x15): undefined reference to `any_name_function'
collect2: ld 返回 1
之所以编译不会报错,是由于C语言规定,对于没有声明的函数,自己主动使用隐式声明。
相当于变成了例如以下代码:
int any_name_function();
int main(int argc, char** argv)
{
double x = any_name_function();
return 0;
}
2 带来的问题
2.1 隐式声明函数名称恰好在链接库中存在,但返回非int类型
前面给出的样例。并不会造成太大影响。由于在链接阶段非常easy发现存在的问题。
然而以下这个样例则会造成莫名的执行时错误。
#include <stdio.h>
int main(int argc, char** argv)
{
double x = sqrt(1);
printf("%lf", x);
return 0;
}
gcc编译链接
[smstong@centos192 test]$ gcc -c main.c
main.c: 在函数‘main’中:
main.c:6: 警告:隐式声明与内建函数‘sqrt’不兼容
[smstong@centos192 test]$ gcc main.o
执行结果
1.000000
编译时会给出警告,提示隐式声明与内建函数’sqrt’不兼容。gcc编译器在编译时可以自己主动在经常使用库头文件(内建函数)中查找与隐式声明同名的函数,如果发现两者并不同样。则会依照内建函数的声明原型去生成调用代码。这往往也是程序猿预期的想法。
上面的样例中隐式声明的函数原型为:
int sqrt(int);
而相应的同名内建函数原型为:
double sqrt(double);
终于编译器依照内建函数原型进行了编译。达到了预期效果。
然而gcc编译器的这样的行为并非C语言的规范,并非全部的编译器实现都有这样的功能。
同样的源代码在VC++2015下编译执行的结果却是:
VC++编译
warning C4013: “sqrt”没有定义;如果外部返回 int
执行结果
2884223.000000
显然。VC++编译器没有没有所谓的“内建函数”,仅仅是简单的依照隐式声明的原型,生成调用sqrt函数的代码。由于返回类型和參数类型的不同。导致错误的函数调用方式。产生莫名奇异的执行时错误。
对着这样的情况,由于返回类型的不同,两种编译器都可以给出警告信息,至少能引起程序猿的注意。而以下这样的情况,则更加隐蔽。
2.2 隐式声明函数名称恰好在链接库中存在。且返回int类型
測试代码例如以下:
#include <stdio.h>
int main(int argc, char** argv)
{
int x = abs(-1);
printf("%d", x);
return 0;
}
此时。由于隐式声明的函数原型与gcc的内建函数原型全然同样。所以gcc不会给出不论什么警告,结果也是正确的。
而VC++则仍然会给出警告:warning C4013: “abs”没有定义。如果外部返回 int。
不管怎样,隐式声明的函数原型与库函数全然同样,所以链接执行都是没有问题的。
以下,略微修改一下代码:
#include <stdio.h>
int main(int argc, char** argv)
{
int x = abs(-1,2,3,4);
printf("%d", x);
return 0;
}
gcc下编译链接没有不论什么报错。
gcc编译链接
[smstong@centos192 test]$ gcc -c main.c
[smstong@centos192 test]$ gcc main.o
可见。gcc的内建函数机制并不关心函数的參数。仅仅是关心函数的返回值。
vc++编译链接
warning C4013: “abs”没有定义;如果外部返回 int
尽管这个样例的执行结果都是正确的,可是这样的正确是“碰巧”的,由于额外的函数參数并没有影响到结果。这样的偶然正确是程序中要避免的。
3 编程中注意事项
C语言的隐式函数声明。给程序猿带来了各种困惑,给程序的稳定性带来了非常坏的影响。不知道当初C语言设计者是怎样考虑这个问题的?
* 为了避免这样的影响,强烈建议程序猿重视编译器给出的关于隐式声明的警告,及时通过包括必要的头文件来消除这样的警告。*
对于gcc来说。前面给出的那个abs(-1,2,3,4)的特殊样例。编译器根本不会产生不论什么警告,仅仅能靠程序猿熟悉自己调用的每个库函数了。
为了避免这样的问题,在C语言的C99版本号中,不管怎样都会给出警告。如gcc使用C99编译上述代码。
gcc -std=c99编译
[smstong@centos192 test]$ gcc -c main.c -std=c99
main.c: 在函数‘main’中:
main.c:5: 警告:隐式声明函数‘abs’
而C++则更严格,直接抛弃了隐式函数声明,对于未声明函数的调用,将直接无法通过编译。
g++编译
[smstong@centos192 test]$ g++ main.c
main.c: In function ‘int main(int, char**)’:
main.c:5: 错误:‘abs’在此作用域中尚未声明
vc++编译(作为C++)
error C3861: “abs”: 找不到标识符
在函数强类型这一点上。C++确实比C更严格,更严谨。
万恶之源:C语言中的隐式函数声明的更多相关文章
- c语言中的隐式函数声明(转)
本文转自:http://www.jb51.net/article/78212.htm 在c语言里面开来还是要学习c++的编程习惯,使用函数之前一定要声明.不然,即使编译能通过,运行时也可能会出一些莫名 ...
- C语言的“隐式函数声明”违背了 “前置声明” 原则
这个问题来源于小组交流群里的一个问题: 最终问题落脚在 : 一个函数在main中调用了,必须在main之前定义或者声明吗? 我在自己的Centos上做了实验,结果是函数不需要,但是结构体(变量也要)需 ...
- 2018-02-17 中文代码示例[译]Scala中创建隐式函数
前言: 学习Scala时, 顺便翻译一下自己有兴趣的文章. 代码中所有命名都中文化了(不是翻译). 比如原文用的是甜甜圈的例子. 原文: Scala Tutorial - Learn How To C ...
- 深入探究js中的隐式变量声明
前两天遇到的问题,经过很多网友的深刻讨论,终于有一个相对可以解释的通的逻辑了,然后我仔细研究了一下相关的点,顺带研究了一下js中的隐式变量. 以下文章中提到的隐式变量都是指没有用var,let,con ...
- 关于gcc内置函数和c隐式函数声明的认识以及一些推测
最近在看APUE,不愧是经典,看一点就收获一点.但是感觉有些东西还是没说清楚,需要自己动手验证一下,结果发现需要用gcc,就了解一下. 有时候,你在代码里面引用了一个函数但是没有包含相关的头文件,这个 ...
- Scala 中的隐式转换和隐式参数
隐式定义是指编译器为了修正类型错误而允许插入到程序中的定义. 举例: 正常情况下"120"/12显然会报错,因为 String 类并没有实现 / 这个方法,我们无法去决定 Stri ...
- Scala 深入浅出实战经典 第62讲:Scala中上下文界定内幕中的隐式参数实战详解
王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载: 百度云盘:http://pan.baidu.com/s/1c0noOt ...
- javascript中的隐式类型转化
javascript中的隐式类型转化 #隐式转换 ## "+" 字符串和数字 如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+将进行拼接操作. 如果其中一个操作数是对 ...
- C语言中可变参数的函数(三个点,“...”)
C语言中可变参数的函数(三个点,“...”) 本文主要介绍va_start和va_end的使用及原理. 在以前的一篇帖子Format MessageBox 详解中曾使用到va_start和va_end ...
随机推荐
- 2011 luogu P1311 选择客栈
题目描述 丽江河边有 nn 家很有特色的客栈,客栈按照其位置顺序从 1 到 n 编号.每家客栈都按照某一种色调进行装饰(总共 k 种,用整数 0 ~ k-1 表示),且每家客栈都设有一家咖啡店,每家咖 ...
- python3.x Day6 协程
协程:#定义来自牛人alex博客协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程.协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈 ...
- Django框架基础知识12-中间件及上下文处理器
Django中间件(Middleware) 是一个轻量级.底层的“插件”系统,可以介入Django的请求和响应处理过程,修改Django的输入或输出. django 中的中间件(middleware) ...
- 嵩天老师的零基础Python笔记:https://www.bilibili.com/video/av13570243/?from=search&seid=15873837810484552531 中的15-23讲
#coding=gbk#嵩天老师的零基础Python笔记:https://www.bilibili.com/video/av13570243/?from=search&seid=1587383 ...
- Oracle数据库学习之存储过程--提高程序执行的效率
存储过程是Oracle开发者在数据转换或查询报表时经常使用的方式之一.它就是想编程语言一样一旦运行成功,就可以被用户随时调用,这种方式极大的节省了用户的时间,也提高了程序的执行效率.存储过程在数据库开 ...
- MySql查询语句的使用实例
一.设计表 1.设计表 查询语句之前先设计四张表:student.teacher.course.score student:sid(学号).sname(姓名).sage(年龄).ssex(性别) te ...
- CentOS7 设置代理
大多数公司的网络都使用局域网加代理上网,也就是说上外网必须使用公司指定的代理服务器,这有几个好处: 1. 首先代理可以一定程度提高浏览速度,因为可以将更多的网页缓存在代理服务器上,需要的时候直接拿就很 ...
- var声明的成员变量和函数内声明的变量区别
1.函数内部,有var声明的是局部变量,没var的,声明的全局变量. 2.在全局作用域内声明变量时,有var 和没var声明的都是全局变量,是window的属性.通过变量var声明全局对象的属性无法通 ...
- 【组合数模板】HDU 6114 Chess
http://acm.hdu.edu.cn/showproblem.php?pid=6114 [思路] 就是求C(m,n) [板] #include<iostream> #include& ...
- 《大话设计模式》Python版代码实现
上一周把<大话设计模式>看完了,对面向对象技术有了新的理解,对于一个在C下写代码比较多.偶尔会用到一些脚本语言写脚本的人来说,很是开阔眼界.<大话设计模式>的代码使用C#写成的 ...