疑难解答(1)

疑难解答(1)

学了一段时间的C语言,我向哈基米问了几个问题


Q:我现在正在使用visual studio 2026,为什么我使用scanf的时候会显示scanf不安全?

A:

Visual Studio 给你的报错通常是 C4996,这并不是说 scanf 这个函数坏了,而是因为它有一个先天缺陷不检查边界

1. 缓冲区溢出(Buffer Overflow)

想象一下,你申请了一个只能装 5 个字符的数组 char a[5],但是你在 scanf("%s", a) 的时候,用户如果不听话,输入了 100 个字符。

  • scanf 也是个“老实人”,用户给多少它就往内存里塞多少。
  • 结果:多出来的字符会溢出,覆盖掉数组后面原本不属于它的内存区域。这可能导致程序崩溃,甚至被黑客利用(这就是著名的“缓冲区溢出攻击”)。

2. 微软的解决方案

微软为了安全,推出了 scanf_s 等一系列带 _s (Secure) 后缀的函数。它们要求你明确告诉它“这块内存有多大”,如果输入超过这个大小,函数就会报错而不是让程序崩溃。


Q:为什么

1
2
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

不会报错

为什么

1
2
#include <stdio.h>
#define _CRT_SECURE_NO_WARNINGS

换一个顺序就又报错了?

A:

这是一个关于 C 语言预处理(Preprocessing) 机制的超级硬核知识点。

你要明白,#include <stdio.h> 并不是一句咒语,它的真实作用是:stdio.h 这个文件里的几千行代码,在编译前原封不动地“复制粘贴”到你的代码里。

场景 1:正确写法(#define 在前)

1
2
3
4
5
// 1. 这一行先执行。编译器此时心里记下了:有一个叫 _CRT_SECURE_NO_WARNINGS 的记号。
#define _CRT_SECURE_NO_WARNINGS

// 2. 这一行执行。编译器打开 stdio.h 文件开始复制。
#include <stdio.h>

发生了什么?stdio.h 的源文件深处(其实是在 Microsoft 的 corecrt.h 里面),写着类似这样的逻辑:

1
2
3
4
5
6
7
8
9
// stdio.h 内部的逻辑示意图
#ifndef _CRT_SECURE_NO_WARNINGS // 如果还没有定义这个记号
// 开启这一堆报错,告诉用户 scanf 不安全
#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 0
// ... 触发报错的代码 ...
#else
// 如果已经定义了这个记号
// 那我就闭嘴,不报错了
#endif

因为你在引入头文件之前就已经打好了招呼(定义了宏),所以头文件里的开关就被正确关闭了。

场景 2:错误写法(#include 在前)

1
2
3
4
5
6
7
8
// 1. 编译器先打开了 stdio.h,把里面的代码复制进来了。
#include <stdio.h>

// 此时编译器读到了 stdio.h 里的判断:
// "咦?现在还没有定义 _CRT_SECURE_NO_WARNINGS 呀?那我就开启报错模式!"

// 2. 你才慢悠悠地定义这个宏……
#define _CRT_SECURE_NO_WARNINGS

发生了什么? 晚了! 当你定义这个宏的时候,stdio.h 里的代码已经被处理过了,报错开关已经被打开了。这就好比你都要进门被保安拦下了,你才想起来去办通行证,那肯定来不及了。


Q:为什么一定要叫 int main(){},不能叫 int Hello(){}

A:

1. 约定俗成的入口

想象一下,操作系统(Windows)就像是一个邮递员,你的程序就像是一栋大楼。

  • 当你想运行程序时,Windows 会去你的代码里找一个特定的门牌号,这个门牌号在 C 语言标准里被规定为 **main**。
  • 如果你把函数名改成了 Hello,邮递员(操作系统)拿着信(执行权)转了一圈,发现找不到 main 这个门牌号,它就不知道该从哪里开始执行你的代码。

2. 链接器报错

如果你强行把 main 改成 Hello,VS 会报一个 LNK2019LNK1120 错误,提示类似: unresolved external symbol _main (无法解析的外部符号 main) 这就意思是:编译器找了一圈,没找到程序的启动按钮。

3. 特殊情况(稍微超纲一点点)

其实是可以改的,但不是给初学者用的。 在做 嵌入式开发 或者 写操作系统内核 时,程序员可以通过修改“链接器脚本”来指定程序的入口是 Start() 或者是 Reset_Handler()。但在标准的 Windows 应用程序开发中,请死死守住 int main() 这个入口。


疑难解答(1)
http://example.com/2025/12/06/疑难解答(1)/
作者
王柏森
发布于
2025年12月6日
许可协议