C++如何防止数组越界

  1. 去年
    去年Sakura 重新编辑
    #include <iostream>
    using namespace std;
    
    int main() {
    	int N, mnt, i;
    	int Counts[51]={0};
    	cin >> N;
    	for (i = 0; i < N; i++) {
    		cin >> mnt;
    		Counts[mnt]++;
    	}
    	for (i = 0; i < 55; i++) {//在这里越界了
    		if (Counts[i]!=0) {
    			cout << i << ":" << Counts[i] << endl;
    		}
    	}
    	return 0;
    }

    输入:

    1
    10

    输出:

    10:1
    52:4197120
    54:2053284352

    这段代码使用g++编译之后可以运行,但是随后就会读到其他的内存地址上
    那么有什么办法能够阻止数组越界呢?例如在数组越界的时候让程序崩溃掉而不是读到不该读的内存

    @Sakura 原题目是要求统计100000个随机的0-50的数字出现频率,那么显然用数组是很不错的做法啊
    而这个我是不小心把遍历数组的上界写大了,发现不同的编译器处理起来结果不一样,所以想到了这个问题

    于是为什么你会觉得这是“显然是很不错的做法”?是因为你一下子想到了数组而没想到用别的嘛?另外你有没有想过“结果不一样”是什么原因?
    正常的选型思路先分析清楚你需要什么之前,忘了哪些东西能被实现支持。你这里的算法也就要求能放50个计数的容器,并不是数组那么具体的东西。在这个基础上,想到用std::vector毫不费力,而不是先想到数组再绕一圈回来想到怎么满足避免不可预期的行为。(硬说的话,确定类型的精确的解是A<std::size_t, 50>,其中A是受参数50作为容量的最小值约束的容器,在C++中不好直接表达,就这个问题也没啥用。)
    作为实现者,你本来就应当保证整个过程中都只会涉及well-defined的操作,然而你所谓的“处理起来结果不一样”就不是,所以遇到这个问题就足够说明你思路的缺陷了。之后分析非预期行为会拐到所谓“内存地址”之类不确定的实现细节上,也同样表明这一点。
    须知,至少对C++这样的语言,选择静态类型的问题上,你的第一印象很可能会有后遗症——不管你要的是可行解还是最优解。对现在的C++,看到数组或者指针类型基本就说明代码馊了;这样的类型是非常特定场景才合适的东西。你需要了解什么时候引入合适的抽象。

  2. 1.写代码的时候时刻检查是否越界
    2.用vector或者array
    3.自己写一个带越界检查的数组类

  3. 去年Sakura 重新编辑

    @OriBeta 1.写代码的时候时刻检查是否越界
    2.用vector或者array
    3.自己写一个带越界检查的数组类

    第三个看起来是个不错的方法
    Vector也不错,operator[]不检查下标,vec.at(n)检查

  4. #include <iostream>
    #include <vector>
    using namespace std;
    
    int main() {
    	int N, mnt, i;
    	std::vector<int> Counts;
    	for (i = 0; i < 51; i++) {
    		Counts.push_back(0);
    	}
    	cin >> N;
    	for (i = 0; i < N; i++) {
    		cin >> mnt;
    		Counts[i]++;//这里其实仍然会存在越界的问题,如果要防止恐怕只能加一个检查了
    	}
    	for (i = 0; i < 51; i++) {
    		if (Counts.at(i)!=0) {
    			cout << i << ":" << Counts.at(i) << endl;
    		}
    	}
    	return 0;
    }
  5. 为什么你会想到用数组这种东西。

  6. 感觉问题的目的不太明确。先补基础 吧。

  7. @幻の上帝 为什么你会想到用数组这种东西。

    原题目是要求统计100000个随机的0-50的数字出现频率,那么显然用数组是很不错的做法啊
    而这个我是不小心把遍历数组的上界写大了,发现不同的编译器处理起来结果不一样,所以想到了这个问题

  8. 去年幻の上帝 重新编辑

    @Sakura 原题目是要求统计100000个随机的0-50的数字出现频率,那么显然用数组是很不错的做法啊
    而这个我是不小心把遍历数组的上界写大了,发现不同的编译器处理起来结果不一样,所以想到了这个问题

    于是为什么你会觉得这是“显然是很不错的做法”?是因为你一下子想到了数组而没想到用别的嘛?另外你有没有想过“结果不一样”是什么原因?
    正常的选型思路先分析清楚你需要什么之前,忘了哪些东西能被实现支持。你这里的算法也就要求能放50个计数的容器,并不是数组那么具体的东西。在这个基础上,想到用std::vector毫不费力,而不是先想到数组再绕一圈回来想到怎么满足避免不可预期的行为。(硬说的话,确定类型的精确的解是A<std::size_t, 50>,其中A是受参数50作为容量的最小值约束的容器,在C++中不好直接表达,就这个问题也没啥用。)
    作为实现者,你本来就应当保证整个过程中都只会涉及well-defined的操作,然而你所谓的“处理起来结果不一样”就不是,所以遇到这个问题就足够说明你思路的缺陷了。之后分析非预期行为会拐到所谓“内存地址”之类不确定的实现细节上,也同样表明这一点。
    须知,至少对C++这样的语言,选择静态类型的问题上,你的第一印象很可能会有后遗症——不管你要的是可行解还是最优解。对现在的C++,看到数组或者指针类型基本就说明代码馊了;这样的类型是非常特定场景才合适的东西。你需要了解什么时候引入合适的抽象。

  9. 长岛冰茶

    9楼 2017年11月14日 物理版主

    @幻の上帝 于是为什么你会觉得这是“显然是很不错的做法”?是因为你一下子想到了数组而没想到用别的嘛?另外你有没有想过“结果不一样”是什么原因?
    正常的选型思路先分析清楚你需要什么之前,忘了哪些东西能被实现支持。你这里的算法也就要求能放50个计数的容器,并不是数组那么具体的东西。在这个基础上,想到用std::vector毫不费力,而不是先想到数组再绕一圈回来想到怎么满足避免不可预期的行为。(硬说的话,确定类型的精确的解是A<std::size_t, 50>,其中A是受参数50作为容量的最小值约束的容器,在C++中不好直接表达,就这个问题也没啥用。)
    作为实现者,你本来就应当保证整个过程中都只会涉及well-defined的操作,然而你所谓的“处理起来结果不一样”就不是,所以遇到这个问题就足够说明你思路的缺陷了。之后分析非预期行为会拐到所谓“内存地址”之类不确定的实现细节上,也同样表明这一点。
    须知,至少对C++这样的语言,选择静态类型的问题上,你的第一印象很可能会有后遗症——不管你要的是可行解还是最优解。对现在的C++,看到数组或者指针类型基本就说明代码馊了;这样的类型是非常特定场景才合适的东西。你需要了解什么时候引入合适的抽象。

    用数组并不是什么大问题吧。。。我现在写计算物理用的基本都是数组,主要是越界检查这种习惯的养成的问题

  10. @幻の上帝 于是为什么你会觉得这是“显然是很不错的做法”?是因为你一下子想到了数组而没想到用别的嘛?另外你有没有想过“结果不一样”是什么原因?
    正常的选型思路先分析清楚你需要什么之前,忘了哪些东西能被实现支持。你这里的算法也就要求能放50个计数的容器,并不是数组那么具体的东西。在这个基础上,想到用std::vector毫不费力,而不是先想到数组再绕一圈回来想到怎么满足避免不可预期的行为。(硬说的话,确定类型的精确的解是A<std::size_t, 50>,其中A是受参数50作为容量的最小值约束的容器,在C++中不好直接表达,就这个问题也没啥用。)
    作为实现者,你本来就应当保证整个过程中都只会涉及well-defined的操作,然而你所谓的“处理起来结果不一样”就不是,所以遇到这个问题就足够说明你思路的缺陷了。之后分析非预期行为会拐到所谓“内存地址”之类不确定的实现细节上,也同样表明这一点。
    须知,至少对C++这样的语言,选择静态类型的问题上,你的第一印象很可能会有后遗症——不管你要的是可行解还是最优解。对现在的C++,看到数组或者指针类型基本就说明代码馊了;这样的类型是非常特定场景才合适的东西。你需要了解什么时候引入合适的抽象。

    嗯,还是我之前的思路不对

  11. @长岛冰茶 用数组并不是什么大问题吧。。。我现在写计算物理用的基本都是数组,主要是越界检查这种习惯的养成的问题

    不管是之后修改代码还是让别人读,抽象不当从来都是问题,只不过刷题用的一次性代码问题看起来没那么突出而已。
    如果目的是用高级语言表达正确的逻辑,越界检查这样的操作甚至都不应该需要成为习惯;应该成为习惯的是使用接口自觉注意满足前置条件(首要的前提是得知道是啥),而不是依赖是否存在越界检查这样的实现细节,剩下的让语言规则隐含的不变量保证。当然,越界检查也可以设计为接口的行为,像vector::at;不过之前提的文档已经讨论过为啥artificially widening narrow contracts不是好主意了。
    不过比起数组,更大的问题在于为什么你得用会让你想到用数组的古董C++……

  12. zhonglingshan1

    12楼 2017年11月14日 化学版主, 技术版主

    前几天在知乎上面看到了个类似的问题……
    鉴于楼上已经说得非常清楚了,不罗嗦了 /<<

  13. 我是大缺弦

    13楼 2017年11月14日 化学版主

    @幻の上帝 不管是之后修改代码还是让别人读,抽象不当从来都是问题,只不过刷题用的一次性代码问题看起来没那么突出而已。
    如果目的是用高级语言表达正确的逻辑,越界检查这样的操作甚至都不应该需要成为习惯;应该成为习惯的是使用接口自觉注意满足前置条件(首要的前提是得知道是啥),而不是依赖是否存在越界检查这样的实现细节,剩下的让语言规则隐含的不变量保证。当然,越界检查也可以设计为接口的行为,像vector::at;不过之前提的文档已经讨论过为啥artificially widening narrow contracts不是好主意了。
    不过比起数组,更大的问题在于为什么你得用会让你想到用数组的古董C++……

    说得很好0。0

 

后才能发言