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

StackOverflow https://stackoverflow.com/questions/437816

  •  22-07-2019
  •  | 
  •  

Вопрос

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

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;
}
Это было полезно?

Решение

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

int printf(char*, ...);

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

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

printf("%d");

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

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

Другие советы

То, что происходит, выглядит следующим образом.

printf("%d", m);

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

Так как «что-то идет», некоторые странные вещи случаются, когда вы помещаете туда переменные. Любой целочисленный тип, меньший чем int, выглядит как int - такие вещи. Также ничего не нужно отправлять.

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

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

Удачи.

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

Он получил int из стека.

http://en.wikipedia.org/wiki/X86_calling_conventions

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top