错误解决
编译和链接的错误
要想使用VS的Debug模式来调试程序,那么首要的前提是程序能够正常启动,也就是程序的问题不能出现在"C源文件 --> C可执行程序"的过程中。
我们都知道,在这个过程中,程序会经历编译(广义)和链接两大过程,所以在讲Debug模式之前,我们先给大家讲一下C程序启动时的编译错误和链接错误。
注意
VS的Debug模式用于调试运行时的错误,而不是编译或链接错误。编译和链接错误必须在程序运行之前解决。
编译错误
编译错误(Compile-time Errors)出现在代码的阶段,表示编译失败。,比如:
- 包含了错误的、不存在的头文件。(预处理阶段)
- 忘记在语句结尾加上分号。
- 小括号、中括号或大括号不匹配。
- 类型不匹配,尝试将一个字符串赋值给一个整数类型的变量等。
- 使用未声明的变量。
- ...
在VS当中运行下列代码:
int main(void) {
a = 100;
return 0;
}
这就是一个典型的编译报错。可以通过查看VS的错误列表窗口来检查这个错误:
当然,这个过程需要明确的是:
VS中的C语言代码编译是由内嵌的编译器MSVC完成的,编译错误的报错信息也是这个编译器给出的,然后通过VS的图形界面显示。
链接错误
链接错误(Linking Errors)出现在代码的链接阶段,表示链接失败。,比如:
- 调用函数时,把函数的名字写错了。比如想调用printf函数,但是写成了print。
- 忘记包含头文件。比如使用printf函数但忘记写#include语句。
- 没有定义一个函数却使用它。
- ...
在VS当中启动下列代码:
// 没有#include <stdio.h>语句
int main(void){
printf("hello world!\n");
return 0;
}
这就是一个典型的链接报错。可以通过查看VS的错误列表窗口来检查这个错误:
和编译报错信息是编译器给出的一样,链接错误的信息也是链接器给出的。
无论是编译还是链接错误,大多都是简单的语法问题、或主观疏忽导致的。熟练的程序员,程序中即便出现这样的问题,也往往能很快速的解决。
思考:为什么这些与函数调用相关的错误是链接器发出的链接错误呢?为什么不是编译错误呢?
首先我们先谈两个关于函数的概念:
指以;
分号结尾的函数头。
比如:
int test(); // 函数的声明语法
指包含函数体的一个带有实现的,真正可以调用的函数。
比如:
int test() {} // 函数的定义语法,只要带有{}就是一个带实现可调用的函数
接下来我们思考标题的问题,这实际上还是由C程序编译和链接,生成可执行文件的过程决定的。在上一节,我们已经讲解了下图:
链接的过程允许将多个.c
源文件合并成一个可执行程序,所以:
- 某个
.c
源文件中的代码完全可以调用其它.c
源文件中实现的函数 - 某个
.c
源文件中的代码完全可以调用外部库函数
所以在编译的过程中,编译器并不会检查调用的某个函数是否真正定义(即函数是否有真正的实现),最多只会检查它是否存在声明。
而到了链接阶段,链接器会将源代码中所有可能存在的外部引用进行解析,此时如果:
- 调用了一个完全不存在的函数或者写错了函数名
- 忘记包含头文件
- ...
链接器就无法真正将函数的定义(实现)合并到目标文件中,此时链接器就会报错。(不报错不行了,再不报错程序就执行了,但此时代码显然无法执行)
Error C4996
由于微软在Visual Studio 2013中不建议再使用C的传统库函数scanf
、strcpy
、sprintf
等,所以直接使用这些库函数会提⽰C4996错误:
VS建议采用带_s
的函数,如scanf_s
、strcpy_s
,但这些并不是标准C函数。要想继续使用scanf
、strcpy
、sprintf
等函数,有三种解决办法
- 在源文件中添加以下指令就可以避免这个错误提⽰:
#define _CRT_SECURE_NO_WARNINGS
//这个宏定义最好要放到.c文件的第一行
- 或者使用这个
#pragma warning(disable:4996)
- 在项目设置中取消安全检查