Malloc(0) массив в Windows Visual Studio для C позволяет программе работать отлично

Программа на C - это алгоритм Дамро-Левенштейна, который использует матрицу для сравнения двух строк. На четвертой строчке main(), Я бы хотел malloc() память для матрицы (2d массив). В тестировании я malloc'd (0), и он все еще работает отлично. Кажется, что бы я ни положил в malloc(), программа все еще работает. Почему это?

Я скомпилировал код с помощью команды "cl" в командной строке разработчика Visual Studio и не получил никаких ошибок.

#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;
        }

    }
}

4 ответа

Решение

относительно malloc(0) часть:

Из справочной страницы malloc(),

malloc() Функция выделяет размер байтов и возвращает указатель на выделенную память. Память не инициализирована. Если размер равен 0, то malloc() либо возвращает NULL или уникальное значение указателя, которое впоследствии может быть успешно передано free() ,

Итак, возвращаемый указатель либо NULL или указатель, который может быть привязан только к free() Вы не можете ожидать разыменования этого указателя и сохранения чего-либо в ячейке памяти.

В любом из вышеперечисленных случаев вы пытаетесь использовать недопустимый указатель, который вызывает неопределенное поведение.

Как только программа достигает UB, вывод этого не может быть оправдан в любом случае.

Одним из главных результатов UB также является "работа в порядке" (как и ожидалось).

Тем не менее, следуя аналогии

"Вы можете выделить распределение нулевого размера, вы просто не должны разыменовывать его"

некоторые из приложений отладчика памяти намекают на то, что использование malloc(0) потенциально небезопасно и красные зоны заявления, включая призыв к malloc(0),

Вот хорошая ссылка, связанная с темой, если вам интересно.

относительно malloc(<any_size>) часть:

В общем, доступ из связанной памяти - это снова UB. Если вам случится получить доступ за пределами выделенной области памяти, вы все равно будете вызывать UB, и предполагаемый результат не может быть определен.

FWIW, сам C не навязывает / не выполняет какую-либо проверку границ самостоятельно. Таким образом, вы не "ограничены" (читается как "ошибка компилятора") в доступе из связанной памяти, но это вызывает UB.

Стандарт C гласит:

Если размер запрошенного пространства равен нулю, поведение определяется реализацией; возвращаемое значение должно быть либо нулевым указателем, либо уникальным указателем. [7.10.3]

Поэтому мы должны проверить, что говорит ваша реализация. Вопрос говорит "Visual Studio", поэтому давайте проверим страницу Visual C++ для malloc:

Если размер равен 0, malloc выделяет элемент кучи нулевой длины в куче и возвращает действительный указатель на этот элемент.

Итак, с Visual C++ мы знаем, что вы получите правильный указатель, а не нулевой указатель.

Но это всего лишь указатель на элемент нулевой длины, так что на самом деле с этим указателем ничего нельзя сделать, кроме как передать его free, Если вы разыменуете указатель, коду разрешается делать все, что он захочет. Вот что подразумевается под "неопределенным поведением" в языковых стандартах.

Так почему же это работает? Наверное потому что malloc вернул указатель как минимум на несколько байтов действительной памяти, так как самый простой способ malloc дать вам действительный указатель на элемент нулевой длины означает, что вы действительно запросили хотя бы один байт. И тогда правила выравнивания округляют до 8 байтов.

Когда вы разыменовываете начало этого выделения, у вас, скорее всего, есть некоторая действительная память. То, что вы делаете, строго незаконно, непереносимо, но с этой реализацией, скорее всего, сработает. Когда вы будете индексировать дальше, вы, вероятно, начнете портить другие структуры данных (или метаданные) в куче. Если вы включите в него даже отца, вы все чаще будете терпеть крах из-за попадания на страницу без карты.


Почему стандарт позволяет malloc(0) быть определенной реализацией вместо того, чтобы просто требовать, чтобы он возвращал нулевой указатель?

С указателями иногда бывает полезно иметь специальные значения. Наиболее очевидным является нулевой указатель. Нулевой указатель - это просто зарезервированный адрес, который никогда не будет использоваться для действительной памяти. Но что, если вам нужно другое специальное значение указателя, которое имеет какое-то значение для вашей программы?

В темные дни перед стандартом некоторые mallocs позволяет эффективно резервировать дополнительные специальные значения указателя, вызывая malloc(0), Они могли бы использовать malloc(1) или любой другой очень маленький размер, но malloc(0) дал понять, что вы просто хотели зарезервировать и адрес, а не фактическое пространство. Таким образом, было много программ, которые зависели от этого поведения.

Между тем, были программы, которые ожидали malloc(0) вернуть нулевой указатель, так как это то, что их библиотека всегда делала. Когда специалисты по стандартам посмотрели на существующий код и на то, как он использует библиотеку, они решили, что не смогут выбрать один метод из другого, не "сломав" часть кода. Таким образом, они позволили поведению malloc оставаться "определяемым реализацией".

Тебе просто повезло. C не выполняет строгой проверки границ, потому что это снижает производительность. Думайте о программе на Си как о шумной вечеринке, происходящей в частном здании, где снаружи находится полиция ОС. Если кто-то бросает камень, который остается внутри клуба (пример недействительной записи, которая нарушает соглашение о праве собственности в рамках процесса, но остается в границах клуба), полиция не видит этого и не предпринимает никаких действий. Но если камень брошен и он опасно летит из окна (пример нарушения, замеченного операционной системой), полиция ОС вмешивается и закрывает вечеринку.

Кажется, что бы я ни положил в malloc(), программа все еще работает. Почему это?

int** t = malloc(0);
int *data = t + yl + 1;

t + yl + 1 является неопределенным поведением (UB). Остальной код не имеет значения.

Если t == NULLдобавление 1 к нему - UB, так как добавление 1 к нулевому указателю является неверной математикой указателя.

Если t != NULLдобавление 1 к нему - это UB, так как добавление 1 к этому указателю больше 1 за пределами выделяемого пространства.


С UB математика указателя может работать как надежда, как обычно malloc() выделяет большие куски, не обязательно требуемый маленький размер. Это может произойти сбой на другой платформе / машине или другой день или фаза луны. Код не является надежным, даже если он работает с легким тестированием.

Другие вопросы по тегам