C++ KNN开发:注释cout后数组元素值异常是否与内存泄漏有关
KNN算法位图数据计算异常问题
问题现象
基于二维数组实现KNN算法时,最初节点仅存储X、Y坐标程序运行完全正常;改为使用16*16二维数组存储位图数据重构逻辑后出现反常问题:
- 问题出在
classify()函数内计算各节点与测试样本距离的for循环中 - 保留循环内的
cout打印距离语句时,距离计算、排序输出完全正常 - 注释该条
cout语句后,会出现距离值丢失小数精度、出现随机无意义值、零值消失等异常,最终排序结果全部为无效垃圾数据 - 注释其他位置打印语句不会触发该异常,仅该条
cout的存在与否决定程序运行结果是否正确
问题复现代码
#include <iostream> #include <math.h> using namespace std; //A node struct Node { int groupValue; int col = 16; //We only have to update these to reflect the bitmap dimensions int row = 16; int bitmap[16][16]; float distance = -1; //distance is the distance this node is from another //Overloading this operator to make swapping them with insertion sort easier Node& operator =(const Node& n) { for(int a = 0; a < row; a++) { for(int b = 0; b < col; b++) { bitmap[a][b] = n.bitmap[a][b]; } } distance = n.distance; groupValue = n.groupValue; return *this; } }; void printNode(Node n) { for(int a = 0; a < 16; a++) { for(int b = 0; b < 16; b++) cout << n.bitmap[a][b] << "."; cout << endl; } } struct Group { int frequency = 0; int id; }; void insertionSort(Node arr[], int n) { int i, key, j; for (i = 1; i < n; i++) { key = arr[i].distance; j = i - 1; while (j >= 0 && arr[j].distance > key) { Node temp = arr[j+1]; arr[j + 1] = arr[j]; arr[j] = temp; j = j - 1; } arr[j + 1].distance = key; } } //Takes two bitmaps and find the distance between each pixel //using the formula d=sqrt(sum((pixel_1-pixel_2)^2)) float bitmapDistance(Node n1, Node n2) { Node differences; //Node's bitmap data will be the differences //squared of each pixel from n1 and n2. float diff; for(int a = 0; a < n1.row; a++) { for(int b = 0; b < n1.col; b++) { diff = n1.bitmap[a][b] - n2.bitmap[a][b]; differences.bitmap[a][b] = diff * diff; //Squared difference } } //Now we need to sum up the squared differences that we stored earlier float sum; for(int a = 0; a < n1.row; a++) { for(int b = 0; b < n1.col; b++) { sum += differences.bitmap[a][b]; } } //Now just take the square root of the sum and return it if(sum < 0.01) //To fix some weird rounding issues I added this. 0.01 is arbitrary. return 0; return sqrt(sum); } /* *Classify the node using KNN algorithm *Multiple groups can be used, and we count how many neighbors *belong to each group. *n is the total amount of nodes *k is the number of nearby neighbors */ int classify(Node arr[], int n, int k, Node node) { //Two switches for sampling, recorded as binary (0-3) Group g0; g0.id = 0; Group g1; g1.id = 1; Group g2; g2.id = 2; Group g3; g3.id = 3; Group groupFrequency[4] = {g0, g1, g2, g3}; //For each neighbor of the given node (node) we use the distance formula //and set it's distance member variable to this measurement for(int a = 0; a < n; a++) { arr[a].distance = bitmapDistance(node, arr[a]); //cout << arr[a].distance << ","; //<===============PROBLEM AREA=============== } cout << endl << "----------" << endl; insertionSort(arr, n-1); for(int a = 0; a < n; a++) cout << arr[a].distance << ","; cout << endl << "----------" << endl; //Look at all the nearest neighbors and see which group they belong to //Increase a counter for each group the neighbor belongs to for(int i = 0; i < k; i++) { if(arr[i].groupValue == 0) groupFrequency[0].frequency++; else if(arr[i].groupValue == 1) groupFrequency[1].frequency++; else if(arr[i].groupValue == 2) groupFrequency[2].frequency++; else if(arr[i].groupValue == 3) groupFrequency[3].frequency++; } cout << "Neighboring group frequencies:" << endl; cout << "Group Zero: " << groupFrequency[0].frequency << endl; cout << "Group One: " << groupFrequency[1].frequency << endl; cout << "Group Two: " << groupFrequency[2].frequency << endl; cout << "Group Three: " << groupFrequency[3].frequency << endl; cout << "-------------------------------------------------" << endl; //Now we just need to look at the most common frequency and classify our node int highestFrequency = 0; int groupNumber = -1; //If -1 is returned, something went wrong. for(int a = 0; a < 4; a++) { if(groupFrequency[a].frequency >= highestFrequency) { highestFrequency = groupFrequency[a].frequency; groupNumber = groupFrequency[a].id; } } return groupNumber; } int main() { Node nodeArray[200]; for(int c = 0; c < 100; c++) { for(int a = 0; a < 16; a++) { for(int b = 0; b < 16; b++) { nodeArray[c].groupValue = 0; nodeArray[c].bitmap[a][b] = rand()%10; } } } for(int c = 100; c < 200; c++) { for(int a = 0; a < 16; a++) { for(int b = 0; b < 16; b++) { nodeArray[c].groupValue = 1; nodeArray[c].bitmap[a][b] = 50;//rand()%10+50; } } } Node testNode; for(int a = 0; a < 16; a++) { for(int b = 0; b < 16; b++) { testNode.bitmap[a][b] = 50;//rand()%10+50; } } int num = classify(nodeArray, 200, 189, testNode); cout << "Test node has a group classification of: " << num << endl << endl; printNode(testNode); cout << endl << "last node in array:" << endl; printNode(nodeArray[199]); cout << endl << bitmapDistance(testNode, nodeArray[199]) << endl << "first node in array:" << endl; printNode(nodeArray[0]); cout << bitmapDistance(testNode, nodeArray[0]); }
问题根因
该问题和内存泄漏完全无关,属于C++典型的未定义行为,共有3个核心bug:
- 未初始化局部变量:
bitmapDistance函数中用于累加像素差平方和的float sum;没有显式初始化,局部变量存储在栈上,初始值为栈内存中残留的随机垃圾值,累加计算的结果完全不确定。添加cout语句时会改变栈内存布局,凑巧让sum所在栈位置的残留值接近0,看起来结果正常;去掉cout后栈布局变化,sum初始值为随机数,直接导致计算结果错误。 - 插入排序类型错误:插入排序中用于暂存距离值的
key被定义为int类型,而实际距离是float浮点值,赋值时会直接截断小数部分,最后又将整型的key写回节点的distance成员,必然导致精度丢失。 - 插入排序范围错误:调用插入排序时传入的长度参数是
n-1,200个节点仅排序前199个,最后一个节点的距离值不会参与排序,直接输出会出现异常值。
另外自定义的赋值运算符重载没有拷贝col和row成员,虽然当前固定尺寸下不触发问题,但后续修改位图尺寸时会出现拷贝错误。
修复方案
按以下步骤修改代码即可解决问题:
- 初始化所有局部变量,将
bitmapDistance中的float sum;改为float sum = 0.0f; - 修正插入排序的key类型,将
int i, key, j;改为int i, j; float key;,避免浮点值被截断 - 修正插入排序调用参数,将
insertionSort(arr, n-1);改为insertionSort(arr, n);,确保所有节点参与排序 - 补全赋值运算符逻辑,在
operator=中添加col = n.col; row = n.row;,保证节点拷贝完整
这类"加打印就正常、删打印就出错"的现象,绝大多数都是未初始化变量、数组越界写入导致的未定义行为,和打印语句本身没有关系,打印语句只是改变了内存布局凑巧掩盖了问题。
内容的提问来源于stack exchange,提问作者tohad94993
相关产品推荐
相关产品推荐

