C语言数组深度剖析:从内存布局到指针与动态实现
我学C语言头一年最绕不过去的坎就是数组。书上说数组和指针差不多可实际写起来总是出各种莫名其妙的问题数组越界不报错、函数传参后长度变了、二维数组传进函数连编译都过不了。后来啃完内存模型、把指针算术搞明白才发现数组所有“坑”背后其实都有一致的逻辑。这篇就把我这些年调试数组攒下来的经验一次性说透从内存布局到指针纠缠再到排序、查找、反转、动态数组的实现细节适合刚学完语法正准备刷题的新手也适合复习C语言准备笔试的在校生。1. 数组到底是什么内存模型与下标真相很多人学数组是先从语法入手的int a[5];然后知道下标从0开始a[0]到a[4]。语法能做题但一旦遇到指针移动、数组名做参数这类问题缺了内存模型的理解就会两眼一抹黑。1.1 连续内存块与下标偏移数组的本质是一块连续的内存区域元素挨个排列之间没有空隙。int a[5]在32位系统上就是5个连续的4字节单元总共占用20字节。下标为什么会从0开始因为下标本质不是“第几个”而是“偏移量”。数组名指向首元素的地址a[3]的真正含义是从首元素地址向后偏移3 * sizeof(int)个字节然后读取那4个字节。偏移从0算起所以第一个元素偏移量为0也就是a[0]。这个理解能推导出很多结论。比如a[3]完全可以写成*(a 3)两者等价编译器内部就是按指针算术处理的。再比如你不小心写了3[a]这种看起来离谱的写法——它其实也合法因为它会被解析成*(3 a)加法交换律一用结果和a[3]一模一样。我看过不少面试题拿这个当彩蛋考人背后的原理就是偏移量。注意这里的“偏移量”是指针级别的偏移不是字节级别的偏移。a 3是按元素个数移动实际地址变化是3 * sizeof(int)字节。写成地址表达式则是(char*)a 3 * sizeof(int)加char*是为了按字节计算。1.2 声明、初始化与变长数组数组声明有好几种形态选错的代价不小int a[10];声明未初始化数组栈上分配元素值是垃圾值。int a[10] {0};全体清零。int a[10] {1,2,3};前3个元素给值其余自动置0。int a[] {1,2,3};由初始化列表推断长度为3。int n 5; int a[n];变长数组C99支持。变长数组VLA是个容易踩的坑。它虽然支持用变量指定大小但数组在栈上分配如果n特别大栈溢出直接崩。我见过一个同学把几兆字节的数组扔到栈上程序运行到一半崩溃还以为是算法问题。另一个容易栽的是局部数组的初始化时机。int a[10] {0};看起来是“每次进入函数都清零”但实际上如果这个数组是局部变量每次调用确实会重新初始化所以开销存在而static int a[10];放在函数里只初始化一次默认值为0第二次调用数组还保留上次的数据。很多做状态记录的程序会利用这个特性但如果没意识到这一点会得到诡异的历史残留值。2. 数组与指针两者之间的“亲密关系”与关键区别面试常问“数组和指针是不是一回事”答案是否定的。但要说清楚差别必须区分两个层面表达层面和类型层面。2.1 数组名即指针不完全对数组名在多数表达式中会被“退化”成指向首元素的指针这是所谓“数组名就是指针”说法的来源。但有两个场景数组名不会退化第一sizeof(a)时数组名表示整个数组得到的是整个数组的字节数不是指针大小。int a[10]在64位系统上sizeof(a)是40而sizeof(a 0)是8因为表达式a 0触发了退化结果是int*。第二对数组名取地址时a是指向整个数组的指针类型是int (*)[10]而不是int*。a 1跳过的不是4个字节而是40个字节——直接跳到数组末尾后面。很多人在这里写崩过比如int a[10]; int *p a; // 编译警告类型不匹配正确写法是int (*p)[10] a;。如果只想拿首元素地址直接用a或者a[0]都行。2.2 指针算术与数组遍历遍历数组最经典的方式就是用指针int a[] {10, 20, 30, 40, 50}; for (int *p a; p a 5; p) { printf(%d\n, *p); }循环条件用p a 5而不是p a 4是因为指针比较要求两个指针指向同一个数组的对象或“尾部之后”的位置a 5是合法的尾后指针用于判断结束很安全。这一写法和循环变量方式一比明显更贴近机器模型每次p不是地址加1字节而是加sizeof(int)字节。需要强调的是减法操作p - q得到的是两个指针之间的元素个数前提是它们指向同一个数组。如果把两个不同数组的指针相减属于未定义行为结果不可预期。笔试里见过不少人把两个无关数组的地址拿来算差值甚至以为能得到内存距离这种操作在栈布局下可能“碰巧能算出数”但依赖的是未定义行为换编译器就翻车。2.3 数组作为函数参数时的退化函数形参写成void f(int a[])和void f(int *a)在参数声明层面完全等价数组符号会退化为指针。这就带来一个经典问题函数内部用sizeof(a)得到的不是调用方数组的真实大小而是指针大小。void print_array(int a[]) { printf(size: %zu\n, sizeof(a)); // 结果根据平台是8或4而不是数组字节数 }所以你在函数里没法靠sizeof(a)/sizeof(a[0])求元素个数。必须在调用方算好或者额外传一个长度参数。我见过不少人在封装一个“排序函数”时内部用这个除法求长度结果排序范围只覆盖前两个元素64位平台上8/42数据纹丝不动还以为是排序算法写错了。正确的传参姿势是void print_array(int *a, int n) { for (int i 0; i n; i) { // ... } }也可以传“指针长度”的组合这几乎是C语言处理数组的标准惯例。2.4 数组与指针的常见混淆场景请尤其注意二维数组和指针数组的区别。int a[3][4]是真正的二维数组内存上12个元素连续排布。它的类型是“数组的数组”a[0]是一个长度为4的数组同时也退化成指向第一个子数组首元素的指针类型为int*。a退化成指向第一个子数组的指针类型为int (*)[4]。int *p[4]则是一个数组里面的每个元素都是int*。两者一旦写混编译器直接报错或产生语义迥异的代码。典型例子是函数参数void f(int a[][4], int rows); // 正确第二维必须明确 void g(int *p[4], int n); // 这是指向4个int指针的数组把二维数组写成void f(int **a)是新手重灾区。虽然对于指针数组传参可以这样用但真正的二维数组在内存里是连续的一块不是“指向一维数组指针的数组”两种内存布局完全不同int**解引用时找的是指针而不是直接去找数据最终导致错误的数据访问。3. 多维数组与字符数组实操多维数组和字符数组是C语言里让很多人头疼的第二片区域因为这里既有布局问题又有字符串终止符带来的边界问题。3.1 二维数组的内存布局与访问int a[3][4]在内存中按行优先排列先放第0行的4个元素再放第1行再放第2行。形象一点说它就是一块连续内存被切成了3行4列。访问a[i][j]时编译器实际计算的是*(a i*4 j)或者*(*(a i) j)两者计算结果相同。这也解释了为什么第二维必须是常量——编译器需要每行长度才能计算行的偏移。当你需要动态生成行数不确定的二维数组时可以有两条路固定第二维第一维用变长数组int a[rows][4];用指针数组int *a[rows];然后每行用malloc分配一维数组。第二种方式的灵活性更高但要记得每行单独释放很容易漏掉free。如果追求高性能不少底层库会把二维数组“拍平”成一维数组比如用data[row * cols col]访问这样既能用malloc一次分配完又能减少二级指针的间接跳转缓存命中率也更好。这个技巧在图像处理、矩阵运算里非常常见。3.2 字符数组与字符串终止符的坑C语言没有真正的字符串类型字符串由字符数组模拟靠末尾的\0结束。这个设计听着简单实操中至少有三个坑第一数组长度必须给\0留位置。char s[5] hello;在C中会报错或造成越界因为hello实际占6个字节包括末尾的\0。正确写法是char s[6];。第二strcpy、strlen等字符串函数都依赖\0判断结束。如果数组里没有终止符strlen会一直往后读直到在某处碰巧遇到0字节这时返回的长度是任意的可能远大于数组容量。这既是“字符串逆序”这类题目出错的主要原因也是内存越界读的隐患。第三用printf(%s, s)输出字符数组时同样依赖终止符。我见过有人把非字符串内容用%s打印一串乱码后面跟着垃圾数据查半天才发现是忘记在末尾置\0。提示char *s hello;和char s[] hello;完全不同。前者是字符串字面量一般存放在只读区修改它会崩溃后者是数组存放在栈上可读写。笔试常拿这个考人记住“能否修改”是区分二者的关键。3.3 数组传参的三种写法给函数传数组写法人人能背但背后的选择逻辑值得说。下面三种写法都可以void f1(int *a, int n); void f2(int a[], int n); void f3(int a[10], int n); // 这里的10只是文档作用编译器忽略三种写法在参数类型解析上等价全部退化成int*。所以不要指望void f(int a[10])能限制调用方只能传10个元素的数组——实际上传20个元素也没问题编译器不检查。如果你确实想限制“指向数组的指针”应该这样void f(int (*a)[10]) { // 这里 a 指向整个长度为10的数组 }这种写法限制了调用参数是一个长度为10的数组的指针只有array这种写法才传得进来。虽然平时用得少但在某些嵌入式驱动接口里用来表达“你传进来的必须是一个完整数组”很有效。4. 核心数组操作的实现与优化光学会声明和传参还不够写代码始终要落到具体操作上反转、排序、查找、插入、删除。这些操作看着基础但每个都有细节值得推敲。4.1 数组反转双指针的经典案例反转数组最朴素的思路是开一个新数组倒着拷贝。但更省内存、更常用的是原地双指针void reverse(int *a, int n) { int left 0, right n - 1; while (left right) { int tmp a[left]; a[left] a[right]; a[right] tmp; left; right--; } }循环条件left right而不是left right是因为当元素个数为偶数时两个指针会正好交叉再交换就出现重复操作为奇数时中间那个元素不需要和自己交换。这个题目看起来简单但把right n - 1写成right n循环里立刻访问越界属于边界测试里的经典陷阱。刷题时建议直接养成“先画双指针位置、再写循环条件”的习惯。4.2 冒泡排序与选择排序细节冒泡排序的教科书写法void bubble_sort(int *a, int n) { for (int i 0; i n - 1; i) { int swapped 0; for (int j 0; j n - 1 - i; j) { if (a[j] a[j 1]) { int tmp a[j]; a[j] a[j 1]; a[j 1] tmp; swapped 1; } } if (!swapped) break; } }这里有两个细节内层循环的n - 1 - i是因为每轮冒泡后末尾 i 个元素已经处于正确位置无需再比较swapped标志在提前有序时跳出循环把平均时间复杂度从O(n²)优化到接近O(n)。真正手写排序时不理解这两个细节经常写出“排序正确但多余比较”的版本。选择排序和冒泡排序容易混淆选择排序每轮找最小值的下标然后和当前位置交换交换次数最多n-1次而冒泡最坏情况交换次数是n(n-1)/2。这个区别在笔试填空题里常被拿来考概念。4.3 最大值的单次遍历法找最大值不用排序一个循环就能解决int max a[0]; for (int i 1; i n; i) { if (a[i] max) max a[i]; }如果要同时找最大值和次大值很多人会先排序但其实单次遍历也可以。思路是维护两个变量最大值和第二大值。int max1 a[0] a[1] ? a[0] : a[1]; int max2 a[0] a[1] ? a[1] : a[0]; for (int i 2; i n; i) { if (a[i] max1) { max2 max1; max1 a[i]; } else if (a[i] max2 a[i] ! max1) { max2 a[i]; } }注意a[i] ! max1这个条件它避免重复元素被当成次大值。比如数组是{5, 5, 3}如果不排除相等情况次大值会等于5而按照通常语义应该返回3。这类边缘条件正是面试官喜欢“随手一问”的点。4.4 在指定位置插入/删除元素数组结构本身不支持动态插入因为它的容量固定。但如果是“在一个长度已知的数组中维护数据”可以手动移动元素实现插入void insert_at(int *a, int *n, int capacity, int pos, int val) { if (*n capacity) return; // 容量不足 if (pos 0 || pos *n) return; // 位置越界 for (int i *n; i pos; i--) { a[i] a[i - 1]; } a[pos] val; (*n); }删除操作类似但是向前移动void delete_at(int *a, int *n, int pos) { if (pos 0 || pos *n) return; for (int i pos; i *n - 1; i) { a[i] a[i 1]; } (*n)--; }插入的位置pos允许等于*n因为它表示追加到尾部删除的位置则必须小于*n。这些判断条件在多种容器实现里都是完全一致的逻辑理解了之后写动态顺序表、写内存池管理都通用。5. 动态数组的几种实现思路静态数组的问题很明显大小写死运行时无法扩展。遇到“不知道要存多少个数据”的场景就要上动态数组。5.1 malloc/realloc 实现可变长数组C标准库提供了malloc、realloc、free三个函数来管理堆内存动态数组就是建立在它们之上的。int *data malloc(sizeof(int) * capacity);当数据量达到容量上限时扩展的思路是int *new_data realloc(data, sizeof(int) * new_capacity); if (new_data ! NULL) { data new_data; capacity new_capacity; }这里有两个关键点第一realloc可能失败返回NULL。如果直接把返回值赋给原指针data realloc(data, new_cap)失败时会丢失原指针造成内存泄漏。稳妥的做法是先存到临时变量判断非NULL后再赋值。第二扩容策略不建议“每次加1”而建议“翻倍”或“乘以1.5”。因为每次realloc可能涉及内存搬迁和元素拷贝如果只增加少量空间总拷贝成本会是O(n²)。翻倍扩容能让均摊成本降到O(1)这也是很多高级语言动态数组的底层策略。if (size capacity) { int new_capacity capacity 0 ? 4 : capacity * 2; int *new_data realloc(data, sizeof(int) * new_capacity); if (new_data NULL) { // 错误处理保留data } else { data new_data; capacity new_capacity; } }5.2 用结构体封装数组裸指针 单独的容量变量用起来容易散更稳妥的做法是用结构体把“指针、容量、大小”打包typedef struct { int *data; int size; int capacity; } IntArray; void array_init(IntArray *arr) { arr-capacity 4; arr-size 0; arr-data malloc(sizeof(int) * arr-capacity); } void array_push(IntArray *arr, int val) { if (arr-size arr-capacity) { // 扩容 } arr-data[arr-size] val; } void array_free(IntArray *arr) { free(arr-data); arr-data NULL; arr-size arr-capacity 0; }封装之后调用方不再需要关心内部的扩容细节只需要array_push、array_pop这样的接口就行。这也是从“写代码”到“设计接口”的一个思路转变数据结构和操作绑定在一起出问题的概率会小很多。警示free之后必须把指针置为NULL。不置NULL的话这个指针就变成“悬空指针”以后再释放一次就会造成double-free错误轻则崩溃重则被攻击者利用。这属于C语言内存管理里的基本素养。5.3 动态二维数组的实现二维动态数组的实现有几种方式我推荐两种。第一种是“指针数组”风格int **matrix malloc(sizeof(int*) * rows); for (int i 0; i rows; i) { matrix[i] malloc(sizeof(int) * cols); }释放时反向释放for (int i 0; i rows; i) { free(matrix[i]); } free(matrix);第二种是“连续内存”风格更接近数组本来的内存模型int *matrix malloc(sizeof(int) * rows * cols); // 访问 matrix[i * cols j] free(matrix);连续风格的优势是只一次malloc只一次free访问时少一次指针跳转在多维数组运算中性能更好。劣势是代码可读性略差需要写i * cols j帮忙计算下标。我自己在写矩阵运算时更常用连续风格因为性能差异在高维循环里是真的能感觉到的。6. 常见的数组边界问题与调试实测数组操作的绝大部分运行时错误来自边界问题。下面把我在教学和开发中遇到的高频问题集中复盘一遍顺便带上排查方法和工具选择。6.1 为什么越界不报错C语言不检查数组越界这是设计选择为了性能编译器不插入边界检查指令。所以a[5]、a[100]在编译时可能毫无警告运行时却会读取或改写数组后面那片未知内存。越界不一定立即崩溃这才是最磨人的。如果访问位置还在本进程的合法内存范围内程序会带着脏数据继续跑直到某次写入破坏了关键变量才在远处爆炸。这类bug的特点是崩溃位置和越界位置往往隔得很远排查时如果只盯着崩溃点看永远找不到根因。我处理这类问题有一个习惯性动作找“最后一次对数组做写入操作的地方”。比如一个循环里a[i] ...先把循环边界打印出来对比数组容量基本上一眼就能定位。6.2 排查越界从打印到Sanitizer最简单的排查是加打印。但面对大数组肉眼比对不可靠建议直接用工具。GCC和Clang都内置了地址消毒器AddressSanitizer编译时加参数就能启用gcc -g -fsanitizeaddress main.c -o main运行程序后如果发生越界读写、栈缓冲区溢出、use-after-free程序会打印具体的错误位置和调用栈。这在调试数组问题时几乎是最强工具比自己猜高效太多。GDB则适合单步调试gdb ./main break 10 run print a[4] x/20dw ax/20dw a的意思是以a为起点显示20个字4字节单位的十六进制数据能直接看到内存里数组前后的残留值很适合确认某个元素是否被意外改写。6.3 字符串操作里最容易翻车的三处第一处是忘了预留\0位置。strcpy(s, abc)写入4字节但只给s分配了3字节。第二处是用gets读取输入这个函数不检查目标容量标准库后来在C11里直接删除了它换成fgets。第三处是strncpy在源字符串长度达到n时不会自动补\0如果你用完它直接调strlen结果可能超过预期。字符串拼接、复制类题目在刷题平台包括PAT乙级这类考试里频繁出现出题人专门喜欢在输入末尾不放换行、字符串长度恰好等于数组容量这些边界情形挖坑。对付这类问题的通用策略是用一个足够大的缓冲数组每步操作都手动保证\0存在。6.4 典型题目复盘反转、找零钱与排序边界刷题网站上很多题目本质上都考数组。以常出现的“字符串逆序”为例常见思路是首尾对调但要注意不能把\0也换到最前面去。正确做法是找到长度n只交换前 n/2 个字符与后 n/2 个字符终止符不动。还有一类“找零钱”类题目比如 PAT 乙级里的“在霍格沃茨找零钱”本质是进制换算加数组处理把三个单位分别换算成最小单位做减法后再换算回去。这里考验的其实不是数组本身而是“数组存储多个部分值按规则还原”的建模能力。做这类题最容易错的地方是在换算时把中间结果存进长度不够的数组导致越界写。冒泡排序在刷题平台出现的频率也很高但要注意很多题目对相同元素有稳定性要求冒泡排序是稳定的选择排序在某些实现里不稳定。要实现稳定排序内层条件必须写成而不是一旦写成相等元素的相对顺序就会被改变。6.5 必看避坑清单数组下标边界for (i 0; i n; i)几乎永远是错误数组下标是0到n-1。sizeof(a)/sizeof(a[0])只能在定义处使用函数参数里用无效。二维数组传参第二维必须写清楚第一维可以省略。realloc返回值不能直接赋值给原指针先检查是否为NULL。字符串数组总要给\0留位置宁可多开一两字节。数组作为函数参数时它退化为指针函数内部无法获知数组长度。比较两个指针前确认它们指向同一个数组或同一个数组的尾后位置。7. 从一个老经验看数组磨炼的价值把数组彻底搞懂收益远不止通过考试和刷题。数组是几乎所有数据结构的基石。链表里的节点虽然用指针串联但很多实现底层仍用数组模拟内存池栈、队列、循环缓冲区的核心就是一个数组加两个下标哈希表的桶可以直接用数组存图论的邻接表每行也可以理解为一个动态数组。理解了数组的连续内存布局、边界管理和扩容策略再去看这些结构会自然很多。我自己在实际写项目时的体会是数组相关的bug往往不是不会语法而是没有建立“内存视角”。拿着“下标小于长度”这种规则去写很机械但脑子里有了“这是一块连续内存我要保证读写都在合法范围内”的画面边界判断就会变成习惯。最后分享一个调试小技巧当你怀疑某个数组元素被意外改写时与其漫无目的地打断点不如把数组地址附近的16字节用 GDB 打印出来对比运行前后的值。很多时候问题不是那个变量写错了而是另一个越界数组把相邻变量给覆盖了。找到越界源头修复只需一行但定位的过程往往需要耐心和工具配合。希望这篇复盘能帮你省下那些不必要的熬夜调试时间。

相关新闻

LongCat 2.5 Preview:运行时符号图谱驱动的深度调试新范式

LongCat 2.5 Preview:运行时符号图谱驱动的深度调试新范式

1. 项目概述:这不是一个普通版本更新,而是一次底层逻辑重写LongCat 2.5 Preview 这个名字乍看像常规迭代,但实际拆开来看,“LongCat”本身不是通用工具名,而是特定技术生态中一个长期演进的调试/分析框架代号&#xff…

2026/10/9 11:47:22 阅读更多 →
面经驱动的工业级Agent开发实战路径

面经驱动的工业级Agent开发实战路径

1. 这不是“面经复读机”,而是一条用真实面试问题反向锻造Agent开发能力的实战路径“从面经开始的agent开发学习”——这标题乍看像求职攻略,实则藏着一套被90%教程忽略的逆向学习法。我带过三十多个AI工程新人,发现一个残酷事实:…

2026/10/9 11:46:25 阅读更多 →
JSP+Servlet学生成绩管理系统实战指南

JSP+Servlet学生成绩管理系统实战指南

简介:这是一套基于JSPServletMySQL实现的学生成绩管理系统完整开发项目,面向计算机专业本科生及Java Web初学者,适用于课程设计、毕业设计选题与Web基础技术实践。资源包共4个文件,含1个SQL数据库脚本(用于初始化学生、…

2026/10/9 12:21:35 阅读更多 →

最新新闻

实测体验!剪流AI超级员工这几个核心功能,才是获客提质增效的关键

实测体验!剪流AI超级员工这几个核心功能,才是获客提质增效的关键

用过多款AI营销工具后发现,真正能拉开产品差距的,从来不是花哨的表层功能,而是那些能切实解决运营痛点、落地性极强的核心能力。很多AI产品只能做到“能生成内容”,但剪流AI超级员工的核心功能,是真正站在企业获客落地…

2026/10/9 12:23:37 阅读更多 →
数量与质量:知识库几百篇,关键在哪

数量与质量:知识库几百篇,关键在哪

结论先说:一个知识库攒到几百篇博客、几百个仓库、索引条目数百条,看着可观,但核心问题不在数量,在质量。数量再多,没掌握、没理解、没法用,也是白瞎。“有"不等于"会”——存了不等于懂了&#…

2026/10/9 12:23:37 阅读更多 →
当下热门的SEO优化服务机构,究竟哪家更靠谱?

当下热门的SEO优化服务机构,究竟哪家更靠谱?

SEO 优化深度痛点分析我们团队在实践中发现,众多企业在 SEO 优化方面面临诸多困境。在流量获取上,SEO 见效极慢,部分企业做了半年优化,关键词排名依旧毫无变动;SEM 则烧钱飞快,谷歌广告点击成本不断攀升&am…

2026/10/9 12:23:37 阅读更多 →
如何找到第 K 短的路径?——从 Dijkstra 到 Yen 算法

如何找到第 K 短的路径?——从 Dijkstra 到 Yen 算法

Dijkstra 和反向 Dijkstra 到底分别在什么场景下使用?图中的第 K 短路径要怎么找?如果允许重复经过节点,或者要求路径不能重复经过节点,处理方式有什么不同?当路径还带有等待时间约束时,算法又该如何改造&a…

2026/10/9 12:23:37 阅读更多 →
10.08 大语言模型研究简报:Reflection AI 发布 Beam 后训练 Scaling 进入新阶段

10.08 大语言模型研究简报:Reflection AI 发布 Beam 后训练 Scaling 进入新阶段

Reflection AI 发布 Beam:501B MoE 亿级 Agentic RL,后训练 Scaling 进入新阶段 发布时间:北京时间 2026 年 10 月 6 日凌晨 状态:Early Access。 Reflection AI 已正式发布 Beam,但截至发布时权重、完整技术报告和 m…

2026/10/9 12:23:37 阅读更多 →
以始为终学习MySQL的术语大全的庖丁解牛

以始为终学习MySQL的术语大全的庖丁解牛

根因 以始为终,和以终为始互为对照。以终为始是锁定终点目标反向倒推方案;而以始为终学习MySQL,核心含义:从最原始的底层起点出发,顺着知识本身的演化顺序,一层层向上搭建完整认知,从根源理解My…

2026/10/9 12:22:35 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/9 6:17:20 阅读更多 →