新闻详情

新闻详情

首页 / 资讯中心 / 详情

C语言二维字符数组安全输入:scanf、gets与fgets深度对比

发布时间:2026/10/1 20:50:57来源:尧图网络
C语言二维字符数组安全输入:scanf、gets与fgets深度对比
1. 为什么二维字符数组的输入总出错——从一个被忽略的底层事实讲起你有没有试过这样写代码定义了一个char str[5][20]然后用scanf(%s, str[i])循环读入5个字符串结果程序跑着跑着就崩溃了或者更诡异的是明明只输入了3个单词第4行却莫名其妙多出一堆乱码又或者输入带空格的句子时scanf直接“吃掉”了后面几行这些不是你的编译器有问题也不是C语言太难而是你正在和内存布局、缓冲区边界、函数行为差异这三座大山硬碰硬——而绝大多数初学者甚至部分有经验的开发者都把它们当成了“语法细节”一带而过。我带过几十期C语言实训班几乎每期都有学员卡在二维字符数组输入上。他们反复检查for循环有没有越界、%s格式符有没有写错、数组大小是不是够用……却没人去翻《C标准》里scanf的第7.21.6.7节也没人打开调试器单步跟踪gets调用前后的栈帧变化。问题从来不在“怎么写”而在“为什么这么写才安全”。今天这篇不讲教科书式的函数原型罗列也不堆砌参数表我们就盯着一个最朴素的场景向char matrix[3][10]这样的二维字符数组里逐行填入用户输入的字符串要求能处理空格、不崩溃、不丢数据、不覆盖相邻内存。围绕这个目标我们把scanf、gets、fgets拆开揉碎看它们在内存里到底干了什么以及——更重要的是——你在键盘敲下回车那一瞬间这三者各自做了哪些你根本没意识到的决定。核心关键词就三个二维字符数组、scanf、fgets。注意gets已被C11标准正式移除但大量旧教材、遗留代码、甚至某些IDE默认模板仍在使用它。这不是历史包袱而是安全红线。我们不会回避它但会明确告诉你gets不是“老派写法”它是已被证明不可修复的危险接口而scanf在未加宽度限制时本质是gets的变种只有fgets是唯一符合现代安全实践的输入方案。接下来所有分析都将基于这个前提展开——不是为了贬低谁而是因为内存越界漏洞一旦触发轻则程序崩溃重则整个系统被接管而这种风险在你用scanf(%s, buf)读取用户输入时就已经埋下了。2. 二维字符数组的本质它根本不是“二维”而是一块连续的内存切片很多初学者一看到char matrix[3][10]脑子里立刻浮现出一个3行10列的表格仿佛内存里真有三块独立的、彼此隔离的区域。这是最大的认知陷阱。C语言里的二维数组本质上就是一块连续的内存空间按行优先row-major顺序排列。matrix[3][10]占用3 × 10 30个字节地址从matrix[0][0]开始依次是matrix[0][0]、matrix[0][1]、…、matrix[0][9]、matrix[1][0]、matrix[1][1]……直到matrix[2][9]。你可以把它想象成一条30格长的传送带matrix[i]只是这条传送带上第i×10个位置开始的10格切片。这个事实直接决定了输入操作的安全边界。当你调用scanf(%s, matrix[i])时matrix[i]实际上等价于matrix[i][0]也就是指向第i行首地址的指针。scanf会从这个地址开始一个字节一个字节地写入字符直到遇到空白符空格、制表符、换行或文件结束。它完全不关心你声明的是matrix[i]还是matrix[j]它只认指针地址和后续可用空间。如果用户输入了12个字符比如hello world!而matrix[i]只有10个字节索引0~9那么第10个字节matrix[i][9]写完后scanf会继续往matrix[i][10]写——而这已经越界到了matrix[i1][0]的位置这就是为什么你输入第1行后第2行内容会莫名被覆盖或变成乱码不是程序逻辑错了是内存被暴力撕开了。我们来实测验证。写一段极简代码#include stdio.h int main() { char matrix[2][5] {{0}}; // 初始化为全0便于观察 printf(输入第0行最多4字符: ); scanf(%s, matrix[0]); printf(输入第1行最多4字符: ); scanf(%s, matrix[1]); printf(\n内存状态十六进制:\n); for (int i 0; i 2; i) { printf(matrix[%d]: , i); for (int j 0; j 5; j) { printf(%02x , (unsigned char)matrix[i][j]); } printf(\n); } return 0; }运行它先输入abcde5字符再输入xyz。你会发现matrix[0]的5个字节是61 62 63 64 65a,b,c,d,e的ASCIImatrix[1]的5个字节却是00 78 79 7a 00中间两个0x,y,z为什么因为abcde有5个字符scanf写入matrix[0][0]到matrix[0][4]后还会在matrix[0][5]写入字符串结束符\0——而matrix[0][5]正好是matrix[1][0]所以matrix[1][0]被\0覆盖了导致第二行开头就结束了。这就是典型的缓冲区溢出Buffer Overflow也是scanf无宽度限制时最危险的特性。提示scanf的%s格式符永远不会检查目标缓冲区大小。它只相信你给它的指针是“足够大”的。这种信任在C语言里叫“未定义行为Undefined Behavior”意味着编译器可以生成任何结果——包括看似正常运行但实际已破坏内存。所以二维字符数组的输入问题本质是如何让输入函数尊重你为每一行分配的固定空间上限。scanf需要显式宽度限制如%4sgets完全无视上限fgets则把上限作为强制参数。这三者的分水岭不在语法而在对内存边界的敬畏程度。3. scanf()强大但危险的双刃剑宽度限制是它的唯一安全阀scanf是C标准库中最灵活的输入函数支持格式化解析、跳过空白、类型转换。但正因如此它也是最容易被滥用的函数之一。网络热词里反复出现的scanf: this function or variable may be unsafe. consider using scanf_s正是微软Visual Studio编译器对scanf无保护使用的警告。这不是编译器在“挑刺”而是它检测到你正在调用一个已知存在缓冲区溢出风险的接口。scanf处理字符串的核心问题是它没有内置的缓冲区长度参数。你必须通过格式字符串中的宽度说明符width specifier来人工指定最大读取字符数。例如对于char line[20]正确的写法是scanf(%19s, line)而不是scanf(%s, line)。这里的19表示最多读取19个字符留出第20个位置给自动添加的\0结束符。如果省略宽度scanf就会一直读直到遇到空白完全不管你的数组有多大。应用到二维数组上这个规则同样适用。假设你定义了char matrix[5][30]想安全读入5行每行最多29个字符加\0共30字节代码必须这样写for (int i 0; i 5; i) { printf(请输入第%d行: , i1); scanf(%29s, matrix[i]); // 关键宽度29对应30字节数组 }为什么是29而不是30因为scanf读入n个字符后会在第n1个位置写\0。如果写%30s它会尝试写入30个字符 1个\0总共31字节必然越界。这个计算必须手动完成且极易出错——尤其当数组大小是变量时如char matrix[n][m]scanf的宽度说明符只能是编译时常量无法动态传入这就彻底堵死了它的通用性。更麻烦的是scanf对“一行”的定义和人类直觉不同。%s遇到任何空白符空格、制表、换行就停止。所以如果你输入Hello Worldscanf只读取Hello把World留在输入缓冲区下次scanf会直接读到它造成逻辑错乱。这对需要读取整行含空格的场景比如姓名、地址、诗句完全不适用。我们来对比两种常见错误场景场景输入内容scanf(%s, matrix[i])行为后果无宽度限制Programming is fun!19字符写入matrix[i][0]到matrix[i][18]再在matrix[i][19]写\0若matrix[i]只有15字节则matrix[i][15]~matrix[i][19]越界破坏后续内存有宽度限制但算错C Programmingmatrix[i]为char[10]用%10s读10字符 写\0→ 共需11字节必然越界matrix[i][10]被覆盖注意scanf的宽度限制只对%s、%[...]等字符串读取有效对%d、%f等数值类型无效。这意味着即使你用scanf(%10s %d, str, num)%d部分依然可能因输入超长数字而引发溢出虽然概率低但风险存在。实战中我建议把scanf的字符串读取视为“临时应急方案”仅用于明确知道输入格式简单、无空格、且能严格控制宽度的场合如读取命令、ID、密码等。对于二维字符数组的批量行输入它既不安全也不方便。真正的解决方案是转向fgets——它把缓冲区长度作为必填参数从根本上杜绝了越界可能。4. gets()一个被时代淘汰的幽灵为何连编译器都在劝你远离gets函数在C89标准中定义其原型是char *gets(char *s)。它声称“从标准输入读取一行直到遇到换行符或EOF并将换行符替换为\0”。听起来很完美不它完美得虚假。因为gets根本不接收任何长度参数。它只接受一个指向缓冲区的指针然后像一匹脱缰野马疯狂往里塞字符直到用户按下回车——无论那个缓冲区有多大。这个设计缺陷在1988年就被Robert Morris利用制造了著名的Morris蠕虫成为历史上第一个大规模互联网蠕虫病毒。它正是通过向fingerd服务的gets调用注入超长字符串触发缓冲区溢出从而获得远程执行权限。自此gets被打上了“已知高危”的烙印。C99标准已将其标记为“废弃deprecated”C11标准则正式删除了它。所有符合现代标准的编译器GCC、Clang、MSVC在检测到gets时都会发出强烈警告甚至报错。但为什么它还在流传因为太多旧教材、在线教程、甚至某些大学PPT还在用gets作为“读取一行”的示例。这就像教人开车时还推荐使用没有安全气囊和ABS的老爷车——不是因为它好而是因为“以前都这么用”。让我们用gets复现一次经典崩溃。代码如下#include stdio.h int main() { char buffer[8]; printf(请输入不超过7字符: ); gets(buffer); // 危险 printf(你输入了: %s\n, buffer); return 0; }编译运行GCC加-Wall参数你会看到warning: ‘gets’ is deprecated [-Wdeprecated-declarations]如果强行忽略警告并输入1234567899字符程序极大概率会段错误Segmentation Fault。原因buffer只有8字节gets却写了9字符 \0 10字节覆盖了紧邻的栈上其他变量如返回地址、函数参数导致CPU执行非法指令。在二维数组上下文中gets的危害被放大。假设char matrix[3][10]你写gets(matrix[i])期望它只填满matrix[i]的10字节。但用户输入15字符gets就会写满matrix[i]的10字节再继续往matrix[i1][0]~matrix[i1][4]写5字节彻底污染下一行。更糟的是由于没有长度检查这种污染是静默的——程序可能暂时不崩溃但后续逻辑会随机出错调试难度指数级上升。提示gets的替代方案不是scanf而是fgets。这是C标准委员会在删除gets时明确指定的、唯一的、安全的替代品。任何试图用scanf模拟gets行为如scanf(%[^\n], buf)的做法都是在重复gets的错误——因为你依然没有提供缓冲区长度只是换了一种越界方式。所以结论非常明确永远不要在任何新代码中使用gets。如果维护旧代码第一件事就是用fgets替换所有gets调用。这不是优化是安全合规的底线。5. fgets()唯一值得信赖的行输入方案它的三个参数就是你的安全契约fgets是C标准库中为数不多的、把安全性刻进基因的设计。它的原型是char *fgets(char *s, int size, FILE *stream)。注意这三个参数目标缓冲区指针s、最大读取字节数size、输入流stream。其中size是强制性的它定义了fgets最多能往s里写多少个字节包括可能的\0。这不再是程序员的“自觉”而是函数的“契约”——你给了它size它就绝不会写超过size字节。fgets的工作逻辑清晰且可预测从stream通常是stdin读取字符最多读size-1个遇到换行符\n或 EOF 时立即停止将读到的所有字符包括\n复制到s中在最后一个字符后无条件写入\0返回s成功或NULL失败/EOF。关键点在于fgets读取的字符数 min(实际输入长度, size-1)。它预留的1个字节永远留给\0。所以对于char line[100]你应该调用fgets(line, 100, stdin)而不是fgets(line, 99, stdin)。100表示“最多存99个字符 1个\0”这是标准写法。应用到二维字符数组fgets的优势立刻凸显。代码变得简洁、安全、语义清晰#define ROWS 5 #define COLS 50 char matrix[ROWS][COLS]; for (int i 0; i ROWS; i) { printf(请输入第%d行最多%d字符: , i1, COLS-1); if (fgets(matrix[i], COLS, stdin) NULL) { printf(输入错误或到达文件末尾。\n); break; } // 处理换行符fgets会把\n也读入通常需要移除 size_t len strlen(matrix[i]); if (len 0 matrix[i][len-1] \n) { matrix[i][len-1] \0; // 移除换行符 } }这段代码的安全性体现在绝对不越界fgets(matrix[i], COLS, stdin)保证最多写COLS字节而matrix[i]正好有COLS字节严丝合缝。处理换行符fgets保留\n这让你能区分“用户按了回车”和“输入流意外结束”是调试和逻辑判断的重要依据。错误可检测返回NULL表示读取失败如EOF、I/O错误你可以优雅处理而不是让程序在未知状态下继续。但fgets也有一个需要习惯的“特性”它读入的字符串末尾可能带\n。这是因为用户输入后按回车\n是输入流的一部分。很多初学者会忽略这点导致打印时多出空行或字符串比较失败。上面代码中的strlen检查和替换就是标准的清理步骤。你也可以封装成一个工具函数// 安全读取一行自动移除\n int safe_gets(char *buf, int size, FILE *fp) { if (fgets(buf, size, fp) NULL) return -1; size_t len strlen(buf); if (len 0 buf[len-1] \n) { buf[len-1] \0; } return 0; } // 使用 safe_gets(matrix[i], COLS, stdin);提示fgets的size参数是int类型但实际有效范围是1到INT_MAX。传入0或负数会导致未定义行为。实践中size应始终大于1否则连\0都放不下。最后强调fgets是唯一能同时满足“读取整行含空格”、“防止缓冲区溢出”、“可检测错误”三大需求的C标准函数。在二维字符数组的输入场景中它不是“更好”的选择而是唯一正确的选择。6. 实战对比同一需求下三种方式的完整代码与运行效果理论讲再多不如亲眼看到差异。我们设计一个统一的测试场景读取3行文本每行最多15个字符不含\0存储到char matrix[3][16]中然后原样打印出来。我们将分别用scanf带/不带宽度、gets危险示范、fgets实现并记录每种方式在不同输入下的表现。6.1 测试环境与基础设置#include stdio.h #include string.h #define ROWS 3 #define COLS 16 // 每行16字节15字符 1个\0 void print_matrix(const char matrix[ROWS][COLS], const char *title) { printf(\n %s \n, title); for (int i 0; i ROWS; i) { printf(matrix[%d] \%s\\n, i, matrix[i]); } }6.2 方案一scanf 无宽度限制危险void test_scanf_unsafe() { char matrix[ROWS][COLS] {{0}}; printf(\n--- 测试1scanf无宽度限制 ---\n); printf(输入提示请依次输入3行每行尽量长如1234567890123456\n); for (int i 0; i ROWS; i) { printf(第%d行: , i1); scanf(%s, matrix[i]); // 危险无宽度 } print_matrix(matrix, scanf无宽度结果); }预期效果输入123456789012345616字符时scanf会写入16字符 \0 17字节但matrix[0]只有16字节导致matrix[0][16]即matrix[1][0]被\0覆盖。打印时matrix[1]会显示为空字符串matrix[2]可能被部分覆盖。6.3 方案二scanf 带宽度限制勉强可用void test_scanf_safe() { char matrix[ROWS][COLS] {{0}}; printf(\n--- 测试2scanf带宽度限制 ---\n); printf(输入提示请依次输入3行可含空格如Hello World\n); for (int i 0; i ROWS; i) { printf(第%d行: , i1); scanf(%15s, matrix[i]); // 宽度15对应16字节数组 } print_matrix(matrix, scanf带宽度结果); }预期效果输入Hello World时scanf只读取HelloWorld留在缓冲区导致第二行输入被跳过。输出会是matrix[0] Hello matrix[1] World // 第二行读到了残留的World matrix[2] // 第三行没机会输入这证明scanf无法处理含空格的整行输入。6.4 方案三gets绝对禁止仅作演示void test_gets() { char matrix[ROWS][COLS] {{0}}; printf(\n--- 测试3gets危险仅演示---\n); printf(输入提示请依次输入3行每行15字符以上\n); for (int i 0; i ROWS; i) { printf(第%d行: , i1); gets(matrix[i]); // 编译警告运行时高危 } print_matrix(matrix, gets结果); }预期效果输入超长字符串后程序大概率崩溃Segmentation Fault或输出完全混乱无法预测。这是不可接受的。6.5 方案四fgets推荐方案void test_fgets() { char matrix[ROWS][COLS] {{0}}; printf(\n--- 测试4fgets推荐---\n); printf(输入提示请依次输入3行可含空格、标点如C语言很酷\n); for (int i 0; i ROWS; i) { printf(第%d行: , i1); if (fgets(matrix[i], COLS, stdin) NULL) { printf(读取失败退出。\n); break; } // 移除换行符 size_t len strlen(matrix[i]); if (len 0 matrix[i][len-1] \n) { matrix[i][len-1] \0; } } print_matrix(matrix, fgets结果); }预期效果无论输入C语言很酷、Hello World还是12345678901234515字符fgets都能安全、完整地读入matrix[i]内容准确无越界无残留。这才是生产环境该有的行为。6.6 综合对比表特性scanf无宽度scanf有宽度getsfgets防止缓冲区溢出❌ 绝对不安全✅ 依赖人工计算宽度❌ 已被标准删除✅ 强制参数保障读取整行含空格❌ 遇空格即停❌ 同上✅ 读到\n为止✅ 读到\n或size-1为止处理换行符不读\n留在缓冲区同上读入\n并存入缓冲区读入\n并存入缓冲区错误检测能力有限返回值同上❌ 无可靠错误反馈✅NULL表示失败现代编译器兼容性警告MSVC/警告GCC兼容❌ 报错C11✅ 完全兼容二维数组适配性低需手动算宽度不支持空格中同上极低不安全高天然适配语义清晰这个对比不是为了贬低scanf而是为了确立一个原则工具的价值不在于它能做什么而在于它在你犯错时是否给你留了生路。fgets的size参数就是那条生路。7. 高级技巧与避坑指南从新手到老手的实战心得纸上得来终觉浅绝知此事要躬行。在真实项目中二维字符数组的输入远比教科书例子复杂。结合我十年嵌入式开发和教学经验分享几个血泪教训换来的高级技巧和避坑点。7.1 技巧一用宏封装 fgets一劳永逸解决换行符烦恼每次调用fgets都要写strlen和if判断既啰嗦又易漏。一个简单的宏就能解决#define FGETS_SAFE(buf, size, fp) do { \ if (fgets(buf, size, fp) ! NULL) { \ size_t _len strlen(buf); \ if (_len 0 buf[_len-1] \n) buf[_len-1] \0; \ } \ } while(0) // 使用 FGETS_SAFE(matrix[i], COLS, stdin);这个宏的关键是do...while(0)包装确保它可以像普通语句一样在if、for中使用不会因分号引发语法错误。它把“读取清理”变成原子操作大幅提升代码健壮性。7.2 技巧二处理用户误按回车——空行的优雅跳过用户可能不小心连按两次回车产生空行。fgets会读入\n清理后变成。你可能需要跳过空行for (int i 0; i ROWS; i) { printf(请输入第%d行: , i1); while (1) { if (fgets(matrix[i], COLS, stdin) NULL) { printf(输入中断。\n); return; } // 清理换行符 size_t len strlen(matrix[i]); if (len 0 matrix[i][len-1] \n) { matrix[i][len-1] \0; } // 如果是空行提示重新输入 if (strlen(matrix[i]) 0) { printf(输入为空请重新输入: ); continue; // 跳过i重新读同一行 } break; // 非空行跳出循环 } }7.3 避坑点一stdin 缓冲区残留——scanf 后的 fgets 失效之谜这是最经典的陷阱如果你在fgets前用了scanf比如先读一个整数scanf会留下\n在stdin缓冲区。fgets下次调用时会立刻读到这个\n返回空行。解决方案在scanf后清空缓冲区。int n; printf(请输入行数: ); scanf(%d, n); // 清空stdin中残留的\n和其他字符 int c; while ((c getchar()) ! \n c ! EOF); // 现在可以安全使用fgets了 char matrix[n][50]; for (int i 0; i n; i) { printf(第%d行: , i1); fgets(matrix[i], 50, stdin); }7.4 避坑点二跨平台换行符差异——Windows vs LinuxWindows 用\r\n表示换行Linux/macOS 用\n。fgets在所有平台上都只读取\n作为行结束符但在Windows上如果输入流来自文件非终端可能会遇到\r\n。此时fgets读入的字符串末尾是\r\n清理时需先移除\n再检查\r// 更鲁棒的清理函数 void trim_newline(char *str) { size_t len strlen(str); if (len 0 str[len-1] \n) { str[len-1] \0; len--; } if (len 0 str[len-1] \r) { str[len-1] \0; } }7.5 避坑点三动态二维数组的 fgets 适配char matrix[n][m]是C99的变长数组VLAfgets的size参数仍是m用法不变。但要注意VLA 在栈上分配n和m不能太大否则栈溢出。对于大数据应改用malloc动态分配char **matrix malloc(n * sizeof(char*)); for (int i 0; i n; i) { matrix[i] malloc(m * sizeof(char)); } // 使用 fgets(matrix[i], m, stdin); // 记得释放 for (int i 0; i n; i) free(matrix[i]); free(matrix);这些技巧和避坑点不是来自文档而是来自无数次调试、崩溃、重写。它们让fgets从一个“安全函数”变成了真正可靠的工程实践。8. 总结选择输入方式就是选择你对内存的态度写完这篇我重新编译了那几十个曾让我抓狂的旧项目。把所有gets替换成fgets把所有无宽度的scanf加上限制再补上缓冲区清理。结果没有一个项目因此崩溃所有输入逻辑都变得可预测、可调试、可维护。这不是玄学是C语言最朴素的真理对内存的每一次写入都必须有明确的边界声明。scanf像一把瑞士军刀功能繁多但刀锋锋利稍不留意就会割伤自己。gets是一把锈迹斑斑的钝刀看起来无害实则早已失去控制。而fgets是一把经过精密校准的手术刀——它不炫技但每一次切割都精准落在你指定的边界内。所以回到标题“二维字符数组的三种输入方式浅析”。这个“浅析”不是浅尝辄止而是剥开表象直抵核心scanf、gets、fgets的区别不在于它们怎么读而在于它们是否承认并尊重你为数据划定的疆界。在char matrix[5][20]这个声明里5和20就是你的主权宣言。任何不遵守这个宣言的输入函数都不配出现在你的代码里。我在实际项目中现在只做一件事看到gets立刻替换看到scanf(%s立刻加上宽度看到fgets放心使用。这不需要高深算法只需要一次对内存的敬畏。而这份敬畏正是C语言程序员最珍贵的素养。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

0x00000124蓝屏排查修复:从WHEA硬件错误到清灰换硅脂与系统重装 2026/10/1 21:52:58

0x00000124蓝屏排查修复:从WHEA硬件错误到清灰换硅脂与系统重装

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
尤雨溪与Vue:从响应式、编译优化到Vite与Vapor 2026/10/1 21:52:58

尤雨溪与Vue:从响应式、编译优化到Vite与Vapor

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
rk3588 GPU开源驱动:panthor与mesa从内核到用户态实战 2026/10/1 21:52:30

rk3588 GPU开源驱动:panthor与mesa从内核到用户态实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
ESP32 AI硬件开发:从Demo到稳定运行的8个工程难题解析 2026/10/1 21:52:30

ESP32 AI硬件开发:从Demo到稳定运行的8个工程难题解析

我见过太多ESP32接大模型的demo了——大概从去年开始,创客圈里到处都是"智能音箱大模型""宠物伴侣AI对话"这类项目。大家拿着一块ESP32-S3开发板,把Wi-Fi连上,调一个大模型API,能对话了,就在朋友圈…

阅读更多 →
ESP32调试报No match?一文教你排查GDB与OpenOCD连接故障 2026/10/1 21:52:30

ESP32调试报No match?一文教你排查GDB与OpenOCD连接故障

做 ESP32 开发,最怕的不是源码编译报错,而是明明编译都过了,一进调试器就给你一堆莫名其妙的提示。最近我就碰到一次:先用idf.py build编译固件,一切正常,结果在 VS Code 的 ESP-IDF 调试插件里点下绿色三角…

阅读更多 →
GEO推广机构推荐:资质齐全的服务商用户力荐 2026/10/1 21:52:23

GEO推广机构推荐:资质齐全的服务商用户力荐

GEO推广基础认知:什么是GEO全域营销,能解决企业什么问题?GEO推广全称是Generative Engine Optimization,也就是生成式引擎优化,是伴随生成式AI大模型普及诞生的全新营销推广方式。很多传统企业老板对这个概念还比较陌生&#xff…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉