Поведение printf при печати% d без указания имени переменной

Я только что столкнулся с какой-то странной проблемой, я пытаюсь напечатать целочисленную переменную, но я забыл указать имя переменной, т.е.

printf("%d");

вместо

printf("%d", integerName);

Удивительно, что программа компилируется, есть выход, и это не случайно. На самом деле, это очень целое число, которое я хотел напечатать, в первую очередь, это m-1.

Понятная инструкция printf будет последовательно выводить m-1 до тех пор, пока программа будет работать... Другими словами, она ведет себя точно так, как если бы инструкция читала

printf("%d", m-1);

Кто-нибудь знает причину такого поведения? Я использую g++ без каких-либо параметров командной строки.

#include <iostream>
#define maxN 100
#define ON 1
#define OFF 0

using namespace std;

void clearArray(int* array, int n);
int fillArray(int* array, int m, int n);

int main()
{
    int n = -1, i, m;
    int array[maxN];
    int found;

    scanf("%d", &n);

    while(n!=0)
    {
        found=0;
        m = 1;
        while(found!=1)
        {
            if(m != 2 && m != 3 && m != 4 && m != 6 && m != 12)
            {
                clearArray(array, n);
                if(fillArray(array, m, n) == 0)
                {
                    found = 1;
                }
            }
            m++;
        }

        printf("%d\n");

        scanf("%d", &n);
    }

    return 0;
}

void clearArray(int* array, int n)
{
    for(int i = 1; i <= n; i++)
        array[i] = ON;
}

int fillArray(int* array, int m, int n)
{
    int i = 1, j, offCounter = 0, incrementCounter;

    while(offCounter != n)
    {
        if(*(array+i)==ON) 
        {
            *(array+i) = OFF;
            offCounter++;       
        }
        else 
        {
            j = 0;
            while((*array+i+j)==OFF)
            {
                j++;
            }
            *(array+i+j) = OFF;
            offCounter++;           
        }
        if(*(array+13) == OFF && offCounter != n) return 1;
        if(offCounter ==n) break;

        incrementCounter = 0;       
        while(incrementCounter != m)
        {
            i++;
            if(i > n) i = 1;
            if(*(array+i) == ON) incrementCounter++; 
        }       
    }

    return 0;
}

Ответ 1

Вы говорите, что "удивительно, что программа компилируется". На самом деле, это не удивительно. C и С++ позволяют функциям иметь списки переменных аргументов. Определение для printf выглядит примерно так:

int printf(char*, ...);

"..." означает, что для функции есть нулевые или более необязательные аргументы. Фактически, одна из основных причин C имеет необязательные аргументы - поддержка семейства функций printf и scanf.

C не имеет специальных знаний о функции printf. В вашем примере:

printf("%d");

Компилятор не анализирует строку формата и не определяет, что отсутствует целочисленный аргумент. Это совершенно законный код C. Тот факт, что вам не хватает аргумента, является семантическая проблема, которая появляется только во время выполнения. Функция printf предположит, что вы предоставили аргумент и ищите его в стеке. Он подберет все, что там будет. Просто случается, что в вашем специальном случае печатается правильно, но это исключение. В общем, вы получите данные об мусоре. Такое поведение будет отличаться от компилятора к компилятору и также будет изменяться в зависимости от того, какие параметры компиляции вы используете; если вы включите оптимизацию компилятора, вы, вероятно, получите разные результаты.

Как указано в одном из комментариев к моему ответу, некоторые компиляторы имеют "lint", подобные возможностям, которые могут фактически обнаруживать ошибочные вызовы printf/scanf. Это связано с компилятором, анализирующим строку формата и определяющим количество ожидаемых дополнительных аргументов. Это очень специфическое поведение компилятора и не обнаруживает ошибок в общем случае. т.е. если вы напишете собственную функцию "printf_better", которая имеет ту же подпись, что и printf, компилятор не обнаружит, отсутствуют ли какие-либо аргументы.

Ответ 2

Что происходит, выглядит так.

printf("%d", m);

В большинстве систем адрес строки будет вставлен в стек, а затем 'm' в виде целого числа (предполагая, что это int/short/char). Нет предупреждения, потому что printf в основном объявляется как 'int printf(const char *, ...);' - значение... означает "что-то идет".

Итак, поскольку "что-то идет", происходят некоторые странные вещи, когда вы помещаете туда переменные. Любой интегральный тип, меньший, чем int, используется как int-вещи, подобные этому. Отправка вообще ничего не работает.

В реализации printf (или, по крайней мере, "простой" реализации) вы найдете использование va_list и va_arg (имена иногда немного отличаются от соответствия). Это то, что использует реализация, чтобы обойти "..." часть списка аргументов. Проблема здесь в том, что нет проверки типа NO. Поскольку проверка типа не проверяется, printf вытаскивает случайные данные из стека выполнения, когда смотрит на строку формата ("%d"), и считает, что предполагается, что следующий 'int'.

Случайный снимок в темноте скажет, что вызов функции, который вы сделали непосредственно перед тем, как printf, возможно, прошел 'm-1' в качестве второго parm? Это одна из многих возможностей - но было бы интересно, если бы это случилось.:)

Удачи.

Кстати, самые современные компиляторы (GCC, я верю?) имеют предупреждения, которые могут быть включены для обнаружения этой проблемы. Я думаю, что Линт тоже. К сожалению, я думаю, что с VC вам нужно использовать флаг /analysis вместо того, чтобы получать бесплатно.

Ответ 4

Вы смотрите в стек. Измените значения оптимизатора, и это может измениться. Измените порядок объявлений ваших переменных (в частности) m. Сделайте m регистрационную переменную. Сделайте m глобальную переменную.

Вы увидите некоторые изменения в том, что происходит.

Это похоже на известные переполнения буфера, которые вы получаете, когда выполняете упрощенные операции ввода-вывода.

Ответ 5

Хотя я бы очень сомневался, что это приведет к нарушению памяти, целое число, которое вы получаете, это undefined мусор.

Ответ 6

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