C语言解析Wavefront obj文件f索引行时其余输出数据异常问题
问题现象
注释掉Wavefront obj文件中f开头的面行解析逻辑时,程序读取其余行的运行结果完全符合预期;一旦取消该段代码的注释,除纹理坐标值外的所有读取数值都会被异常篡改。经初步排查,确认未使用跨所有行检查逻辑持久生效的变量,无法定位问题诱因。
相关代码
obj.h 实现代码
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<cglm/cglm.h> int loadobj(const char* filename, float* vertices, float* texcoords, float* normals, int* indices){ FILE* file = fopen(filename, "r"); char lineheader[128]; int res; int i = 0; int f = 0; int d = 0; int g = 0; float verticesout[24]; float texcoordsout[28]; float normalsout[18]; unsigned int vertexindex[3], texindex[3], normalindex[3]; vec3 vertex; vec2 texcoord; vec3 normal; vec3 vertexindices; vec3 texindices; vec3 normalindices; if(file == NULL){ printf("Failed to open file!\n"); return 1; } while(1){ res = fscanf(file, "%s", lineheader); if(res == EOF){ break; } if(strcmp(lineheader, "v") == 0){ fscanf(file, "%f %f %f\n", &vertex[0], &vertex[1], &vertex[2]); verticesout[i] = vertex[0]; verticesout[i + 1] = vertex[1]; verticesout[i + 2] = vertex[2]; i += 3; }else if(strcmp(lineheader, "vt") == 0){ fscanf(file, "%f %f\n", &texcoord[0], &texcoord[1]); texcoordsout[f] = texcoord[0]; texcoordsout[f + 1] = texcoord[1]; f += 2; }else if(strcmp(lineheader, "vn") == 0){ fscanf(file, "%f %f %f\n", &normal[0], &normal[1], &normal[2]); normalsout[d] = normal[0]; normalsout[d + 1] = normal[1]; normalsout[d + 2] = normal[2]; d += 3; }else if(strcmp(lineheader, "f") == 0){ fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d\n", &vertexindex[0], &texindex[0], &normalindex[0], &vertexindex[1], &texindex[1], &normalindex[1], &vertexindex[2], &texindex[2], &normalindex[2]); vertexindices[g] = vertexindex[0]; vertexindices[g + 1] = vertexindex[1]; vertexindices[g + 2] = vertexindex[2]; texindices[g] = texindex[0]; texindices[g + 1] = texindex[1]; texindices[g + 2] = texindex[2]; normalindices[g] = normalindex[0]; normalindices[g + 1] = normalindex[1]; normalindices[g + 2] = normalindex[2]; g += 3; } } memcpy(vertices, verticesout, sizeof(verticesout)); memcpy(texcoords, texcoordsout, sizeof(texcoordsout)); memcpy(normals, normalsout, sizeof(normalsout)); memcpy(indices, vertexindices, sizeof(vertexindices)); return 0; }
main.c 调用代码
#include<stdio.h> #include<stdlib.h> #include<cglm/cglm.h> #include"obj.h" int main(){ float vertices[24]; float texcoords[24]; float normals[24]; int indices[36]; if(loadobj("model.obj", vertices, texcoords, normals, indices) != 0){ printf("Errors encountered!\n"); } int i; int f; int d; int g; for(i = 0; i < sizeof(vertices)/sizeof(float); i++){ if(i % 3 == 0 && i != 0){ printf("\n"); } if(vertices[i] >= 0){ printf(" "); } printf("%f ", vertices[i]); } printf("\n\n"); for(f = 0; f < sizeof(texcoords)/sizeof(float); f++){ if(f % 2 == 0 && f != 0){ printf("\n"); } printf("%f ", texcoords[f]); } printf("\n\n"); for(d = 0; d < sizeof(normals)/sizeof(float); d++){ if(d % 3 == 0 && d != 0){ printf("\n"); } if(normals[d] >= 0){ printf(" "); } printf("%f ", normals[d]); } printf("\n\n"); for(g = 0; g < sizeof(indices)/sizeof(int); g++){ if(g % 3 == 0 && g != 0){ printf("\n"); } printf("%d ", indices[g]); } printf("\n"); }
问题根因
数值篡改完全是栈内存越界写入导致的,核心问题出在面索引的存储逻辑:
- 你用cglm的
vec3类型定义vertexindices、texindices、normalindices,这类类型本质是长度为3的float数组,仅能合法访问下标0、1、2三个位置。但解析面行时你用变量g做索引,每解析完一个三角面就给g加3,解析第二个面时g值已经是3,访问vertexindices[3]这类位置属于越界写,写入的数据会直接覆盖栈上相邻位置的verticesout、normalsout数组内容,直接篡改之前读好的顶点、法线数据。纹理坐标没被篡改只是因为栈上texcoordsout的位置离越界写的区域更远,刚好没被覆盖到。 - 代码里还存在其他会触发内存错误的隐患:
texcoordsout定义长度为28个float,但调用memcpy拷贝的目标texcoords仅分配了24个float的空间,拷贝长度sizeof(texcoordsout)超出目标缓冲区大小,会写坏栈内存。- 最后拷贝索引数据时,
vertexindices是仅能存3个float的vec3,sizeof(vertexindices)仅12字节,而目标indices数组是用来存所有面索引的36个int长度,拷贝出来的索引数据完全不完整。 - 索引本身是无符号整型值,你用float类型的vec3数组存储整型,再按int类型拷贝解析,本身就会出现数值错乱。
- OBJ格式的索引是从1开始计数的,当前代码读取后没有减1,后续直接当作C数组下标使用会触发越界访问。
修复方案
- 移除索引用的
vec3类型定义,根据模型实际面数给顶点索引、纹理索引、法线索引分配足够长度的整型数组,不要用float类型数组存储整型索引值。 - 统一所有缓冲区的长度,比如
texcoordsout的长度要和传入的texcoords长度匹配,避免memcpy时源缓冲区比目标缓冲区大导致的越界。 - 拷贝索引时按实际存储的索引总长度计算拷贝字节数,不要直接用vec3类型的长度做拷贝参数。
- 读取到OBJ的索引值后统一减1,转换为C语言数组使用的0起始下标。
内容的提问来源于stack exchange,提问作者RadoslavL
相关产品推荐
相关产品推荐

