[英]Windows/Visual Studio going wonky on a perfectly fine C program
[英]Malloc(0)ing an array in Windows Visual Studio for C allows the program to run perfectly fine
C程序是Damereau-Levenshtein算法,它使用矩阵比较两个字符串。 在main()
的第四行,我想为矩阵(2d数组)分配malloc()
。 在测试中,我分配了(0),它仍然运行良好。 看来无论我把malloc()
放在哪里,该程序仍然有效。 为什么是这样?
我在Visual Studio开发人员命令提示符中使用“ cl”命令编译了代码,但未收到任何错误。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
int main(){
char y[] = "felkjfdsalkjfdsalkjfdsa;lkj";
char x[] = "lknewvds;lklkjgdsalk";
int xl = strlen(x);
int yl = strlen(y);
int** t = malloc(0);
int *data = t + yl + 1; //to fill the new arrays with pointers to arrays
for(int i=0;i<yl+1;i++){
t[i] = data + i * (xl+1); //fills array with pointer
}
for(int i=0;i<yl+1;i++){
for(int j=0;j<xl+1;j++){
t[i][j] = 0; //nulls the whole array
}
}
printf("%s", "\nDistance: ");
printf("%i", distance(y, x, t, xl, yl));
for(int i=0; i<yl+1;i++){
for(int j=0;j<xl+1;j++){
if(j==0){
printf("\n");
printf("%s", "| ");
}
printf("%i", t[i][j]);
printf("%s", " | ");
}
}
}
int distance(char* y, char* x, int** t, int xl, int yl){
int isSub;
for(int i=1; i<yl+1;i++){
t[i][0] = i;
}
for(int j=1; j<xl+1;j++){
t[0][j] = j;
}
for(int i=1; i<yl+1;i++){
for(int j=1; j<xl+1;j++){
if(*(y+(i-1)) == *(x+(j-1))){
isSub = 0;
}
else{
isSub = 1;
}
t[i][j] = minimum(t[i-1][j]+1, t[i][j-1]+1, t[i-1][j-1]+isSub); //kooks left, above, and diagonal topleft for minimum
if((*(y+(i-1)) == *(x+(i-2))) && (*(y+(i-2)) == *(x+(i-1)))){ //looks at neighbor characters, if equal
t[i][j] = minimum(t[i][j], t[i-2][j-2]+1, 9999999); //since minimum needs 3 args, i include a large number
}
}
}
return t[yl][xl];
}
int minimum(int a, int b, int c){
if(a < b){
if(a < c){
return a;
}
if(c < a){
return c;
}
return a;
}
if(b < a){
if(b < c){
return b;
}
if(c < b){
return c;
}
return b;
}
if(a==b){
if(a < c){
return a;
}
if(c < a){
return c;
}
}
}
malloc(0)
部分: 从malloc()
的手册页中,
malloc()
函数分配大小字节,并返回指向分配的内存的指针。 内存未初始化。 如果size为0,则malloc()
返回NULL
或可以稍后成功传递给free()
的唯一指针值。
因此,返回的指针为NULL
或只能固定到free()
的指针,您不能期望取消对该指针的引用并将某些内容存储到内存位置。
在上述两种情况下,您都试图使用无效的指针,它会调用未定义的行为 。
一旦程序到达UB,无论如何该输出就无法证明。
UB的主要成果之一就是“运作良好”(正如“错误地”预期的那样)。
就是说,追随类比
“您可以分配零大小的分配,只是不能取消引用它”
一些内存调试器应用程序暗示malloc(0)
的使用可能是不安全的,并对包括对malloc(0)
的调用的语句进行了红色区域划分。
malloc(<any_size>)
部分: 通常,再次访问绑定内存是UB。 如果碰巧在分配的内存区域之外进行访问,则无论如何都会调用UB,并且推测的结果无法定义。
FWIW,C本身并不强加/执行任何边界检查。 因此,您不受“限制”( 读作“编译器错误” )的访问超出范围的内存,但这样做会调用UB。
看来无论我把
malloc()
放在哪里,该程序仍然有效。 为什么是这样?
int** t = malloc(0);
int *data = t + yl + 1;
t + yl + 1
是不确定的行为(UB)。 其余代码无关紧要。
如果t == NULL
,则将UB加1就是UB,因为将1加到空指针是无效的指针数学运算。
如果t != NULL
,则将其加1就是UB,因为对该指针加1超出了分配空间。
使用UB,由于典型的malloc()
分配较大的块(不一定是所请求的较小的块malloc()
,因此指针数学可能会发挥希望。 它可能会在另一个平台/机器上或在月亮的另一天或某个月坠毁。 该代码即使可以进行轻度测试也并不可靠。
你真幸运。 C不执行严格的边界检查,因为它会降低性能。 可以将C程序想象为发生在私人建筑物中的一个喧闹的聚会,在该建筑物中OS警察驻在外面。 如果有人扔一块石头留在俱乐部内(一个无效的写法示例,在该过程中违反了所有权约定,但停留在俱乐部边界内),则警察不会看到它发生,也不会采取任何行动。 但是,如果扔石头并且危险地将其飞出窗外(操作系统注意到的一个违规示例),则OS警察会介入并关闭聚会。
C标准说:
如果请求的空间大小为零,则行为是实现定义的; 返回的值应为空指针或唯一指针。 [7.10.3]
因此,我们必须检查您的实施说明。 这个问题说“ Visual Studio”,所以让我们检查一下Visual C ++页面中的malloc
:
因此,使用Visual C ++,我们知道您将获得一个有效的指针而不是一个空指针。
但这只是一个零长度项的指针,因此,除了将其传递给free
之外,您实际上不能做任何安全的事情。 如果取消引用指针,则允许代码执行其所需的任何操作。 这就是语言标准中“未定义的行为”的含义。
那么为什么它似乎起作用呢? 可能是因为malloc
返回了指向至少几个有效内存字节的指针,因为malloc
向您提供零长度项的有效指针的最简单方法是假装您确实要求至少一个字节。 然后对齐规则会将其四舍五入为8个字节。
当取消引用分配的开始时,您可能有一些有效的内存。 您正在执行的操作严格是非法的,不可移植的,但是使用此实现可能会起作用。 当您对它进行进一步索引时,您可能会开始破坏堆中的其他数据结构(或元数据)。 如果您甚至将父亲编入索引,由于碰到未映射的页面,您崩溃的可能性也会越来越大。
为什么标准允许malloc(0)
定义为实现定义,而不仅仅是要求它返回空指针?
使用指针,有时需要特殊值。 最明显的是空指针。 空指针只是保留的地址,永远不会用于有效的内存。 但是,如果您想要另一个对程序有意义的特殊指针值,该怎么办?
在标准发布之前的黑暗日子里,一些malloc
允许您通过调用malloc(0)
有效地保留其他特殊指针值。 他们本可以使用malloc(1)
或任何其他非常小的大小,但是malloc(0)
明确表明您只是想保留和寻址而不是实际空间。 因此,有许多程序依赖于此行为。
同时,有些程序期望malloc(0)
返回空指针,因为这是他们的库始终执行的操作。 当标准人员查看现有代码及其使用库的方式时,他们决定如果不“破坏”某些代码,就无法选择一种方法。 因此,他们允许malloc的行为保持“实现定义”。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.