
1. Hello World在VS2022里跨出C语言第一步很多人学C语言第一站就卡在Visual Studio 2022的配置上——明明照着网上的教程敲代码结果连个Hello World都跑不出来。倒不是代码写错了而是VS2022的项目设置和C语言之间隔着一层默认C的坑。这篇内容就围绕VS2022环境下的C语言入门来展开重点解决两个问题怎么顺利跑出第一个程序以及怎么对付那个几乎每个人都会撞上的scanf报错。先说环境准备。VS2022安装时在使用C的桌面开发这个工作负载里其实已经包含了C语言的编译器支持因为C编译器本身就兼容编译C代码。你不需要额外装任何东西打开VS2022选择创建新项目在项目模板列表里找到空项目Empty Project然后给项目起个名字比如HelloWorld选择存放位置点创建就行。这里有几个容易踩的细节我第一次带新人时几乎每个人都遇到了项目模板里明确写着C但C语言代码完全可以在这个项目里编译运行原因是VS的编译器会根据源文件的扩展名来决定按什么标准编译。你把文件保存成.c后缀编译器就按C89/C99的规则处理保存成.cpp后缀就按C处理。创建项目后VS默认不会帮你添加源文件需要你自己在解决方案资源管理器里右键源文件文件夹 → 添加 → 新建项 → 选择C文件(.cpp)然后在文件名那里手动改成main.c注意扩展名必须是.c。这两步搞定之后就可以写代码了。打开main.c把下面的内容敲进去#include stdio.h int main() { printf(Hello World\n); return 0; }然后在工具栏上找到本地Windows调试器这个绿色按钮或者直接按CtrlF5程序就能跑起来一个黑色控制台窗口会弹出来显示Hello World。按CtrlF5和按F5的区别在于F5会以调试模式运行程序结束后窗口马上就关了CtrlF5是开始执行不调试程序跑完后会提示按任意键继续方便你看结果。注意如果你是第一次运行可能会弹出一个对话框提示由于调试设置问题无法启动程序这时候只需要按提示点击确定VS会自动设置好调试器路径然后再按一次F5就能跑了。这个阶段的目标只有一个先让编译器、链接器、运行器这套链路通畅起来。只要Hello World能正常打印出来后面学什么都有底气。写在开头的一个提醒如果你用的教材或教程里写的头文件是#include stdio.h而某些老教程用的还是#include stdio.h这是一回事不用纠结。真正的分歧出现在下一节——scanf报错这是几乎所有初学者从复制代码到自己写代码之间必须迈过的一道坎。2. 罪魁祸首scanf报错的本质是什么几乎每个在VS2022里尝试编写scanf代码的人都会看到一个红得像警报一样的错误error C4996: scanf: This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.很多人的第一反应是我代码写错了。其实不是。你的代码在语法上是完全正确的这是VS2022特有的一个善意警告但VS默认把它当成错误来显示。这里需要解释一下背后的原理。微软在Visual Studio的C/C运行库CRTC Runtime Library里增加了一套安全增强函数比如scanf_s、strcpy_s、sprintf_s。这套函数的核心思路是调用时必须显式传入缓冲区大小运行时库会额外检查边界防止缓冲区溢出等安全问题。而传统的scanf、strcpy这类函数因为不检查输入长度被认为是不安全的。微软还做了一件狠事默认开启了SDL检查Security Development Lifecycle。这个检查机制会把对不安全函数的调用直接标记为编译错误而不是警告。如果你用的是Dev-C、Code::Blocks、Linux下的GCCscanf根本不会报这个错。这也是为什么网上很多教程里用scanf而你在VS2022里敲同样的代码却报错的原因——不是你错了是环境不同。具体到VS2022这个错误触发的条件是这样的编译器在编译.c或.cpp文件时如果代码里调用了scanf并且当前项目属性里SDL检查开着预处理器就会自动定义_CRT_SECURE_NO_WARNINGS为未定义状态然后引发C4996。VS2022针对C语言项目默认开启SDL所以默认情况下scanf、strcpy、strcat、sprintf、fopen这些函数都会报类似的问题。这个报错的严重程度是错误Error不是警告Warning会导致程序无法编译生成.exe文件所以你必须处理它才能运行程序。我的建议是不要跳过这一步也不要感觉沮丧。搞懂这个错误的来龙去脉你会顺便理解编译器、运行时库、安全检查这些概念值回票价。关于scanf_s的两句实话网上有人说scanf_s是微软私货不是标准C用它不好也有人说用scanf_s就完了。我的看法是如果你用VS2022做练习用scanf_s完全没问题它跟标准C的scanf在绝大多数场景下行为一致只是入门代码里多一个缓冲区大小参数而已。但如果你要参加考试、跨平台开发或者运行在Linux服务器上必须用标准C的scanf。因此下面我给出了三种解决方案你可以按自己的需求选一种。3. 三种方案破解scanf报错3.1 方案一一劳永逸地关闭SDL检查这是最简单的办法如果你不想改任何代码只想让scanf像在GCC里一样正常工作就改项目属性在解决方案资源管理器里右键项目名称 → 属性。找到配置属性 → C/C → SDL检查。把下拉框从是改成否。点确定然后重新编译。改完之后scanf的C4996错误就消失了代码原样保留不用动一个字符。这个方案的好处是你学的是标准C代码拿到Linux或者Mac上跑行为完全一致不受微软私有扩展影响。特别是如果你用的是谭浩强那类教材教材里的scanf代码都可以直接运行不用临时改成scanf_s。代价也很明显SDL检查是微软提供的一道安全防线它会帮你拦截很多潜在危险的函数调用。我这个项目关掉SDL检查后以后用strcpy、sprintf等函数也不会再报错但这些函数确实存在缓冲区溢出的风险。如果你不是特别在意安全性只是想快速跑通作业这个方案最省事。需要注意的坑项目属性里的配置要选对。如果你当前是Debug配置就把配置下拉框选成Debug如果是Release也要对应改。VS的默认显示通常是Debug但你切换到Release重新编译时SDL又变回是导致同样的报错又出现。所以要么两个配置都改要么干脆把配置下拉框改成所有配置再改一次。3.2 方案二改用scanf_s最符合VS风格的做法如果你想遵循VS的推荐路径就把代码里的scanf改成scanf_s。这是微软官方推荐的做法改动也不大。先看原来的代码#include stdio.h int main() { int age; printf(请输入年龄); scanf(%d, age); printf(你的年龄是%d\n, age); return 0; }改成scanf_s之后是这样#include stdio.h int main() { int age; printf(请输入年龄); scanf_s(%d, age); printf(你的年龄是%d\n, age); return 0; }你可能会问这俩看起来没区别啊区别在于上面的代码能编译通过是因为%d对应的读取目标是单个整数根本不存在缓冲区溢出的风险所以scanf_s在这种情况下和scanf完全兼容。但如果是读取字符串情况就不同了。比如读取一个名字char name[20]; scanf(%s, name); // 标准C写法在VS里报错 scanf_s(%s, name, 20); // scanf_s写法需要多传一个缓冲区长度参数看到没scanf_s处理字符串时要求你额外传入目标缓冲区的大小这里是20它根据这个大小限制读取的字符数量防止越界写入。这是scanf_s相对scanf的核心改进点。然后对比一下C11标准里的带边界检查函数的写法功能标准C传统写法VS中scanf_s写法读取整数scanf(%d, n)scanf_s(%d, n)读取浮点数scanf(%f, f)scanf_s(%f, f)读取字符串scanf(%s, s)scanf_s(%s, s, sizeof(s))读取字符scanf(%c, c)scanf_s(%c, c, 1)括号里的参数个数和含义看起来有点绕但规律其实很好记对数值类型的变量整型、浮点、字符变量scanf_s的用法和scanf完全一样只是函数名多个_s。对数组类型的变量字符串scanf_s在数组名后面多加一个整数用来告诉编译器这个缓冲区能放多少个元素。sizeof(s)在编译期间就会确定是数组的实际大小所以传进去的是一个常量不会有性能影响。但有一点要注意如果数组是字符指针如char *psizeof(p)得到的是指针大小8字节不是缓冲区大小这时候手动写数字才是安全的。我的经验是如果你只在这学期用VS2022交作业以后不太可能在Linux上写C那用scanf_s是最省心的。上学期的我甚至建议新入门的同学直接适应scanf_s省得以后被VS搞烦。3.3 方案三用宏定义压制警告保留标准C写法这个方案兼顾了保留scanf原始写法和不修改项目配置两个需求。只需要在源文件的最顶部所有#include之前加一行#define _CRT_SECURE_NO_WARNINGS但更好的做法是让VS自动加上这个宏定义不需要每次写代码都手敲一行在解决方案资源管理器里右键项目 → 属性。找到预处理器 → 预处理器定义。在编辑框里追加_CRT_SECURE_NO_WARNINGS注意用分号分隔已有的定义。确定重新编译。用宏定义这种做法在编译器层面其实相当于告诉微软我知道这些函数不安全但我就是不用你那套安全版本你也别拿错误卡我。并不是编译器就完全放行了而是C4996错误被抑制了。这个方法有个好处既保留了标准C的代码风格又不影响SDL检查的其他保护功能。不过我在实际使用中发现很多初学者会忘了在代码里写那一行宏定义导致换了新项目又报错。如果你是在多个项目之间切换我更推荐方案一直接在项目属性里把SDL关了一步到位。3.4 三种方案怎么选拿张表做个直观对比方案代码改不改项目配置改不改适合场景缺点关闭SDL检查不改改用标准C教材、想跨平台丢掉微软的安全检查用scanf_s改不改长期用VS2022代码不能直接移植到GCC用宏定义不改改预处理器偶尔用小项目练习容易忘记加在开头个人建议如果你现在还在学习阶段优先用方案一让代码保持标准C的写法。因为C语言是一门写一次到处编译的语言回想一下你自己是不是遇到过网上下载的源码能在GCC编译通过在自己的VS上却一堆红线。很多时候就是因为scanf这类函数被VS特有的规则拦住了。学的标准写法以后换环境就不用改代码。如果把scanf_s当成了默认用法考试时或者去用别的编译器时反而会手足无措。如果你的目标是长期在Windows平台上写C后面还打算接触C那方案二其实更友好。因为VS整个生态都习惯带安全后缀的函数早点适应也就早点融入这个平台的节奏。4. 写了Hello World和scanf之后还会碰到这些问题解决了scanf报错距离写好一个能交互的程序还差一步。因为scanf报错只是VS2022的特产但接下来的很多问题是任何C语言初学者都会遇到的。我把我在带新人过程中见到的典型报错和排查方法整理在下面按遇到的频率排序。4.1 链接错误unresolved external symbol这是一个典型的编译通过、链接失败的错误报错类似于LNK2019: unresolved external symbol _main referenced in function int __cdecl invoke_main(void)这个错误的意思是编译器把你的main.c编译成了目标文件.obj但在链接阶段找不到程序的入口点main函数。常见的引发原因有两个第一你把main写成了mian、miann之类的手误编译器找不到了。这种情况最简单回到代码里检查函数名。第二你创建项目时选择了控制台应用以外的模板或者创建的是C空项目结果VS替你生成了一个文件.cpp里面没有main函数或者你根本没有添加任何源文件就去点编译了。解决方法是确认项目里有且只有一个main函数而且它的签名是int main()或int main(void)或者int main(int argc, char *argv[])。还有一个不太常见但真实存在的坑你添加了源文件但文件后缀是.cpp结果编译器按C标准编译函数的重载签名跟C语言不一致也可能导致链接错误。所以创建源文件时后缀一定写成.c。4.2 程序乱码控制台输出中文变成一堆横线在VS2022里用printf输出中文时有时候你会发现控制台显示的是涓?崌...之类的一串乱码。原因是Windows控制台默认的代码页是GBK代码页936而VS2022默认的源文件编码可能是UTF-8带BOM或没带BOM两者不匹配导致乱码。解决这个问题有几种思路在源文件的头部加上#pragma execution_character_set(utf-8)但这个方法只对VS2015之后版本有效。更通用的做法是把源文件另存为ANSI编码这样字符在内存中就是GBK控制台可以直接显示。或者用system(chcp 65001)把控制台代码页切换成UTF-8但这样要改运行时的环境不是每个程序都方便。我的建议是作为练习阶段尽量先避免在printf里输出复杂中文用英文做输出最省心。等你熟悉了编码的概念再回头处理这个乱码问题会更好理解。4.3 scanf没读到值被缓冲区里的残留字符坑了初学者写一个程序先提示输入年龄再输入姓名经常发现还没输入年龄就直接跳到姓名或者第一次输入后第二次就跳过了。这背后的原因跟scanf的缓冲区机制有关int age; char name[20]; printf(年龄:); scanf(%d, age); // 用户输入 20 然后按回车 // 缓冲区里实际是 2 0 \n // scanf %d 读过 2 0 之后把 \n 留在缓冲区里 printf(姓名:); scanf(%s, name); // %s 读到缓冲区里的 \n不%s 会跳过前导空白这里其实有一个好消息%s格式符会自动跳过空白字符空格、制表符、换行所以这种情况通常没问题。但如果你用的是%c它就会把这个残留的换行符读出来导致后续逻辑出错。典型场景int num; char ch; printf(输入一个数字); scanf(%d, num); printf(输入一个字符); scanf(%c, ch); // 坑这里ch会得到上一次留下的换行符而不是用户新输入的字符这种问题在初学者里特别普遍。解决方法是在读%c之前用一个getchar()把残留的换行符吞掉或者用scanf( %c, ch)在格式串里加一个前导空格让scanf先跳过空白。4.4 scanf输入格式的陷阱逗号、空格与换行scanf的格式串是初学者很容易误解的一个地方。很多人在格式串里写scanf(%d,%d, a, b);想在输入时用逗号分隔两个数但实际操作时发现怎么输都出错。原理是这样的scanf在格式串里的普通字符除了空格、换行、制表符等空白符必须与输入流中的字符精确匹配。如果你在格式串里写了逗号那么用户在键盘上必须敲一个逗号scanf才会继续读取否则它就会停止并返回已经成功匹配的字段数量。更特别的是格式串里的空格不是必须匹配的它表示的是一个广义的空白字符集合。所以有几个常见写法及其对应的输入要求格式串合法输入示例说明scanf(%d%d, a, b)10 20 或 10换行20空白字符自动跳过scanf(%d,%d, a, b)10,20逗号必须出现scanf(%d %d, a, b)10 20和第一种几乎一样scanf(%d-%d, a, b)10-20横线必须出现初学阶段我强烈建议用scanf(%d%d, a, b);这种写法让空白字符自然分隔输入项比强求用户按特定格式输入要稳妥得多。毕竟用户输入错了格式你的程序就读不到数据处理起来很被动。4.5 变量地址忘了加导致程序崩溃scanf的参数要求传指针/地址初学者最容易漏掉取地址符号写出scanf(%d, age);而不是scanf(%d, age);。这时候编译器一般会给出一个警告比如format string %d expects an argument of type int*, but argument has type int。但如果你忽略了警告链接还能通过运行的时候程序会因为scanf试图往一个非法地址写入整数而崩溃或者写入了某个随机内存程序行为变得不可预测。这里有个常见的例外如果是字符串数组比如char name[20];你写scanf(%s, name);不用在name前面加。原因是数组名本身就会退化成指向数组首元素的指针也就是char*类型恰好就是scanf的%s需要的类型。但如果是单个字符变量char c你写scanf(%c, c);仍然需要。我自己见过不少同学在字符指针、字符串数组和单个字符之间搞混。建议刚开始时每次写scanf都确认一遍格式符是%d、%f、%c这类时后面跟的变量都需要加是%s时数组名直接写但如果是char*指针就直接写指针名。5. 初学者最需要养成的两个习惯在经历了Hello World成功、scanf报错解决、缓冲区坑、编码乱码这一整套流程之后你会发现自己其实已经接触到C语言学习中最容易让人放弃的阶段。这个阶段的核心不是代码量而是理解报错、解决问题的能力。5.1 读懂报错而不是背报错很多初学者第一眼看到红色的错误提示就慌了直接跳过去看别人写好的答案。但我观察下来那些进步快的同学都是愿意花时间逐行读报错信息的。VS2022的报错窗口里每一行错误都包含四个关键信息错误代码比如C4996、LNK2019、C2143引发错误的文件路径和行号编译器对问题的描述一个查看帮助的链接我常用的排查思路是先把报错信息复制到记事本里把里面提到的文件路径、行号、函数名圈出来。然后从上到下逐一对照行号是不是我代码里的某一行那个函数名是不是我函数名拼错了有时候问题根本不是报错指向的那一行而是上一行少了一个分号、多了半个大括号编译器只是被牵连了。5.2 每次只改一个地方再运行一次这是我自己从做嵌入式开发项目里养成的习惯一次只修一个错误修完立刻重新编译再继续处理下一个。新手容易犯的毛病是编译报了十几个错误就一口气全改结果越改越乱最后不知道哪里改对了、哪里改错了。实践经验是很多编译错误之间是有连锁反应的。比如一个未定义的变量可能导致后续几十行代码全部报错一个缺少的大括号可能导致后面整段代码被认为是注释或嵌套结构。这种情况下只需要修好第一个错误重新编译后面的错误往往会自动消失大半。修一个、编译一次效率反而高。5.3 动手前先写注释理清思路别急着敲代码。初学者一笔一画照着敲没问题但最好在代码里先把逻辑用中文注释写一遍。比如#include stdio.h int main() { int age; // 1. 提示用户输入年龄 // 2. 用scanf读取输入并保存到age变量 // 3. 打印age的值 // 顺序执行如果有错重点检查第2步 printf(请输入你的年龄); scanf(%d, age); printf(你的年龄是%d岁。\n, age); return 0; }用注释把思路显式化一是能帮自己理清逻辑二是报错的时候能更快定位到具体步骤。我见过很多同学卡在printf和scanf这两行之间反复改其实就是没想清楚程序执行的顺序。6. 我个人实际操作中的一点体会真正把VS2022里的C语言环境调顺之后你会发现后面反而没什么好怕的了。scanf报错这个坎算是微软这套IDE给初学者设置的第一道必答题解决了它你对项目配置、编译器工作原理、运行库差异都能有比课本更直观的感受。如果非要再分享一条经验那就是我在帮别人调试时发现的最常见的连锁反应scanf报错没解决 → 为了试错随手把代码改成了scanf_s → 但格式串写错了 → scanf_s读取失败 → 程序行为异常 → 觉得是断点问题 → 开了F11单步调试 → 又遇到乱码 → 整个人彻底懵了。整个过程就像多米诺骨牌一环扣一环。所以我把顺序建议给你先在项目属性里关掉SDL检查保证代码是标准C然后集中精力解决输入输出逻辑本身等程序跑顺了再决定要不要为了安全改写成scanf_s也无妨。写这篇文章的时候我模拟的正是自己当年初学C语言时在VS2022里栽过的跟头。回过头看这些问题都不复杂只是缺少一个系统性的梳理。希望这篇内容能帮你少走几段弯路让Hello World之后的路走得更稳一点。