Неопределённое поведение с устаревшими объявлениями функций в ANSI C

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.


Стандарт ANSI C определяет понятие прототипа функции, представляющее собой подмножество объявления функции, которое указывает типы входных параметров. Прототипы были введены с целью устранить недостатки, которыми обладают обычные объявления функций.


Таким образом, указание списка типов параметров в круглых скобках прототипа функции обязательно, иначе такое выражение будет признаваться компилятором как устаревшее объявление функции, что может привести к неоднозначным ситуациям, описанным в данной статье.


Устаревшие прототипы


Объявление функции вводит возвращаемый тип функции и её идентификатор в заданную область видимости. Обратите внимание, что не все объявления функций могут считаться прототипами, а лишь те, которые обладают списком типов входных параметров.


Таким образом, первое выражение приведённого ниже кода есть объявление, но не прототип функции. Следующее выражение уже может по праву считаться прототипом, так как специфицирует типы своих параметров:


/* #1 (Устаревшее объявление функции "foo") */
void foo();

/* #2 (Прототип функции "bar") */
void bar(int count, const char *word);

Устаревшие определения


Давайте перенесёмся прямиком в 1972 год (год выхода языка Си) и вспомним, как программисты того времени определяли свои функции. Напомню, что определение функции связывает её сигнатуру с соответствующим исполняемым блоком (телом). Данный код демонстрирует определение функции add в стиле K&R:


void add(right, left, result)
    int right;
    int left;
    int *result; {
    *result = right + left;
}

Как вы уже могли заметить, в данной записи круглые скобки идентифицируют функцию, но не содержат никаких типов входных параметров. Этим же свойством обладают "классические" объявления функций, описанные в предыдущей секции.


Неоднозначные ситуации


Не исключено, что при несоблюдении нового синтаксиса прототипов и определений функций, введённых стандартом ANSI C, возможно возникновение трудно отслеживаемых неоднозначных ситуаций. Рассмотрим пример:


#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <limits.h>

/* Устаревшее объявление функции "print_number" */
void print_number();

int main(void) {
        /* Правильно */
        print_number((double)13.359);
        print_number((double)9238.46436);
        print_number((double)18437);

        /* Разврат и беззаконие */
        print_number(UINT64_MAX);
        print_number("First", "Second", "Third");
        print_number(NULL, "Breakfast", &print_number);
}

void print_number(double number) {
        printf("Предоставленное число: [%f]\n", number);
}

Проанализируем данную программу. Сама по себе правильная функция print_number объявлена без указания списка типов параметров, вследствие чего вы способны вызвать эту функцию с любыми аргументами. Программа скомпилировалась без ошибок и напечатала следующий результат:


$ gcc illegal.c -o illegal -Wall
$ ./illegal
Предоставленное число: [13.359000]
Предоставленное число: [9238.464360]
Предоставленное число: [18437.000000]
Предоставленное число: [0.000000]
Предоставленное число: [0.000000]
Предоставленное число: [0.000000]

Также обратите внимание, что даже с флагом -Wall компилятор gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0 не сгенерировал никаких предупреждений (но было бы крайне желательно).


Исправить данную программу не составит особого труда, достаточно лишь дописать double number в круглых скобках объявления функции print_number на седьмой строчке, после чего любой компилятор, следующий стандарту, укажет на ошибки в функции main():


$ gcc -Wall illegal.c -o illegal
illegal.c: In function ‘main’:
illegal.c:17:22: error: incompatible type for argument 1 of ‘print_number’
         print_number("First", "Second", "Third");
                      ^~~~~~~
illegal.c:7:6: note: expected ‘double’ but argument is of type ‘char *’
 void print_number(double number);
      ^~~~~~~~~~~~
illegal.c:17:9: error: too many arguments to function ‘print_number’
         print_number("First", "Second", "Third");
         ^~~~~~~~~~~~
illegal.c:7:6: note: declared here
 void print_number(double number);
      ^~~~~~~~~~~~
illegal.c:18:22: error: incompatible type for argument 1 of ‘print_number’
         print_number(NULL, "Breakfast", &print_number);
                      ^~~~
illegal.c:7:6: note: expected ‘double’ but argument is of type ‘void *’
 void print_number(double number);
      ^~~~~~~~~~~~
illegal.c:18:9: error: too many arguments to function ‘print_number’
         print_number(NULL, "Breakfast", &print_number);
         ^~~~~~~~~~~~
illegal.c:7:6: note: declared here
 void print_number(double number);
      ^~~~~~~~~~~~

Функции без параметров


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


#include <stdio.h>

/*  Устаревшее объявление функции "do_something" */
void do_something();

int main(void) {
    /* Функцию "do_something" можно вызвать с совершенно
        любыми аргументами */
    do_something(NULL, "Papa Johns", 2842, 1484.3355);
}

void do_something() {
    puts("I am doing something interesting right now!");
}

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


Заключение


На написание данной статьи меня вдохновила книга Стивена Прата "Язык программирования Си. Лекции и упражнения. Шестое издание", а конкретно секция "Функции с аргументами" пятой главы.

Источник: https://habr.com/ru/post/456682/


Интересные статьи

Интересные статьи

Я делаю много ревью для чужого кода на Ансибл и много пишу сам. В ходе анализа ошибок (как чужих, так и своих), а так же некоторого количества собеседований, я понял основную ошибку, ...
Доброго дня У нас имеется несколько облачных кластеров с большим количеством виртуальных машин в каждом. Все это дело у нас хостится в Hetzner'e. В каждом кластере у нас имеется по одной мас...
Покупки в обычном магазине просты и понятны. Условно вы просто снимаете с полки чайник и идёте на кассу, чтобы расплатиться. Бывают и менее очевидные варианты, когда вы покупаете, например, сра...
Бизнес-смыслы появились в Битриксе в начале 2016 года, но мало кто понимает, как их правильно использовать для удобной настройки интернет-магазинов.
Некоторое время назад мне довелось пройти больше десятка собеседований на позицию php-программиста (битрикс). К удивлению, требования в различных организациях отличаются совсем незначительно и...