c语言中的隐式函数声明(转)
本文转自:http://www.jb51.net/article/78212.htm
在c语言里面开来还是要学习c++的编程习惯,使用函数之前一定要声明。不然,即使编译能通过,运行时也可能会出一些莫名其妙的问题。
1 什么是C语言的隐式函数声明
在C语言中,函数在调用前不一定非要声明。如果没有声明,那么编译器会自动按照一种隐式声明的规则,为调用函数的C代码产生汇编代码。下面是一个例子:
|
1 2 3 4 5 |
int main(int argc, char** argv) { double x = any_name_function(); return 0; } |
单纯的编译上述源代码,并没有任何报错,只是在链接阶段因为找不到名为any_name_function的函数体而报错。
|
1 2 3 4 5 |
[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语言规定,对于没有声明的函数,自动使用隐式声明。相当于变成了如下代码:
|
1 2 3 4 5 6 |
int any_name_function(); int main(int argc, char** argv) { double x = any_name_function(); return 0; } |
2 带来的问题
2.1 隐式声明函数名称恰好在链接库中存在,但返回非int类型
前面给出的例子,并不会造成太大影响,因为在链接阶段很容易发现存在的问题。然而下面这个例子则会造成莫名的运行时错误。
|
1 2 3 4 5 6 7 |
#include <stdio.h> int main(int argc, char** argv) { double x = sqrt(1); printf("%lf", x); return 0; } |
gcc编译链接
|
1 2 3 4 |
[smstong@centos192 test]$ gcc -c main.c main.c: 在函数‘main'中: main.c:6: 警告:隐式声明与内建函数‘sqrt'不兼容 [smstong@centos192 test]$ gcc main.o |
运行结果
|
1 |
1.000000 |
编译时会给出警告,提示隐式声明与内建函数'sqrt'不兼容。gcc编译器在编译时能够自动在常用库头文件(内建函数)中查找与隐式声明同名的函数,如果发现两者并不相同,则会按照内建函数的声明原型去生成调用代码。这往往也是程序员预期的想法。
上面的例子中隐式声明的函数原型为:
int sqrt(int);
而对应的同名内建函数原型为:
double sqrt(double);
最终编译器按照内建函数原型进行了编译,达到了预期效果。然而gcc编译器的这种行为并不是C语言的规范,并不是所有的编译器实现都有这样的功能。同样的源码在VC++2015下编译运行的结果却是:
VC++编译
warning C4013: “sqrt”未定义;假设外部返回 int
运行结果
|
1 |
2884223.000000 |
显然,VC++编译器没有没有所谓的“内建函数”,只是简单的按照隐式声明的原型,生成调用sqrt函数的代码。由于返回类型和参数类型的不同,导致错误的函数调用方式,产生莫名奇妙的运行时错误。
对着这种情况,由于返回类型的不同,两种编译器都可以给出警告信息,至少能引起程序员的注意。而下面这种情况,则更加隐蔽。
2.2 隐式声明函数名称恰好在链接库中存在,且返回int类型
测试代码如下:
|
1 2 3 4 5 6 7 8 |
#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(-,,,);
printf("%d", x);
return ;
}
gcc编译链接gcc下编译链接没有任何报错。
[smstong@centos192 test]$ gcc -c main.c
[smstong@centos192 test]$ gcc main.o
vc++编译链接可见,gcc的内建函数机制并不关心函数的参数,只是关心函数的返回值。
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:: 警告:隐式声明函数‘abs'
而C++则更严格,直接抛弃了隐式函数声明,对于未声明函数的调用,将直接无法通过编译。
g++编译
[smstong@centos192 test]$ g++ main.c main.c: In function ‘int main(int, char**)': main.c:: 错误:‘abs'在此作用域中尚未声明
error C3861: “abs”: 找不到标识符vc++编译(作为C++)
在函数强类型这一点上,C++确实比C更严格,更严谨。
c语言中的隐式函数声明(转)的更多相关文章
- 万恶之源:C语言中的隐式函数声明
1 什么是C语言的隐式函数声明 在C语言中,函数在调用前不一定非要声明.如果没有声明,那么编译器会自己主动依照一种隐式声明的规则,为调用函数的C代码产生汇编代码.以下是一个样例: int main(i ...
- C语言的“隐式函数声明”违背了 “前置声明” 原则
这个问题来源于小组交流群里的一个问题: 最终问题落脚在 : 一个函数在main中调用了,必须在main之前定义或者声明吗? 我在自己的Centos上做了实验,结果是函数不需要,但是结构体(变量也要)需 ...
- 深入探究js中的隐式变量声明
前两天遇到的问题,经过很多网友的深刻讨论,终于有一个相对可以解释的通的逻辑了,然后我仔细研究了一下相关的点,顺带研究了一下js中的隐式变量. 以下文章中提到的隐式变量都是指没有用var,let,con ...
- 2018-02-17 中文代码示例[译]Scala中创建隐式函数
前言: 学习Scala时, 顺便翻译一下自己有兴趣的文章. 代码中所有命名都中文化了(不是翻译). 比如原文用的是甜甜圈的例子. 原文: Scala Tutorial - Learn How To C ...
- 关于gcc内置函数和c隐式函数声明的认识以及一些推测
最近在看APUE,不愧是经典,看一点就收获一点.但是感觉有些东西还是没说清楚,需要自己动手验证一下,结果发现需要用gcc,就了解一下. 有时候,你在代码里面引用了一个函数但是没有包含相关的头文件,这个 ...
- C#中的隐式转换
你是否考虑过这个问题:为什么不同类型之间的变量可以赋值,而不需要强制转换类型?如: int i = 1; long l = i; object obj = 1; Exception exception ...
- Scala 中的隐式转换和隐式参数
隐式定义是指编译器为了修正类型错误而允许插入到程序中的定义. 举例: 正常情况下"120"/12显然会报错,因为 String 类并没有实现 / 这个方法,我们无法去决定 Stri ...
- javascript中的隐式类型转化
javascript中的隐式类型转化 #隐式转换 ## "+" 字符串和数字 如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+将进行拼接操作. 如果其中一个操作数是对 ...
- C语言中可变参数的函数(三个点,“...”)
C语言中可变参数的函数(三个点,“...”) 本文主要介绍va_start和va_end的使用及原理. 在以前的一篇帖子Format MessageBox 详解中曾使用到va_start和va_end ...
随机推荐
- ServletContext接口(六)
javax.servlet.ServletContext接口 ServletContext(上下文)是公用的,就是.net中的application,主要用到的就是全局set设置值,get获取值,ja ...
- ubuntu - 常用问题解决命令
查看本机网络通畅 - 网络是否可连接(远程链接时,检验防火墙) ping -c 10 localhost 查看本机进程/端口占用情况(看某一进程是否开启) netstat -tln 具体查看某一进程 ...
- Mysql 5.6主从搭建
mysql设置主从的重要性和必要性不必多说,下面开始详细说明如何搭建主从. 1.主服务器上创建一个用于复制的账户. mysql'; mysql> flush privileges; 2.主服务器 ...
- uoj#119. 【UR #8】决战圆锥曲线(线段树+复杂度分析)
题解 传送门 题解 然而要我来说我感觉只是个爆搜啊-- //minamoto #include<bits/stdc++.h> #define R register #define ll l ...
- 洛谷P4173 残缺的字符串(FFT)
传送门 话说为什么字符串会和卷积扯上关系呢……到底得脑洞大到什么程度才能想到这种东西啊……大佬太珂怕了…… 因为通配符的关系,自动机已经废了 那么换种方式考虑,如果两个字符串每一位对应的编码都相等,那 ...
- [CQOI2012][bzoj2668] 交换棋子 [费用流]
题面 传送门 思路 抖机灵 一开始看到这题我以为是棋盘模型-_-|| 然而现实是骨感的 后来我尝试使用插头dp来交换,然后又惨死 最后我不得不把目光转向那个总能化腐朽为神奇的算法:网络流 思维 我们要 ...
- P1891 疯狂LCM
\(\color{#0066ff}{ 题目描述 }\) 众所周知,czmppppp是数学大神犇.一天,他给众蒟蒻们出了一道数论题,蒟蒻们都惊呆了... 给定正整数N,求LCM(1,N)+LCM(2,N ...
- BestCoder Round #80 待填坑
Lucky Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Submi ...
- [BOI2007]Sequence 序列问题 BZOJ1345
题目描述 对于一个给定的序列a1, …, an,我们对它进行一个操作reduce(i),该操作将数列中的元素ai和ai+1用一个元素max(ai,ai+1)替代,这样得到一个比原来序列短的新序列.这一 ...
- zabbix监控tcp连接数的脚本!!
#!/bin/bash #this script is used to get tcp and udp connetion status #tcp status metric=$ tmp_file=/ ...