Программы
Функции и типы в первом приближении

Функции и типы в первом приближении

Назад, к "первой утилите".

0 программистов ругал сердитый шeф,

Потом уволил одного, и стало их FF.

– Юрий Нестеренко, "0A программистов"

Сейчас мы немного рассмотрим теорию, чтобы заполнить пространство под вопросом о false – будет много букв – сложнее случайно увидеть "ответ".

Но эта теория тут не просто так: наша функция main – хоть и необычная, но функция. Опять же, мы объявили саму функцию main как функцию, возвращающую int – тип данных. Очень хитрый тип данных.

Изображение Изучаем язык программирования Си

Немного о функциях

Итак, в общем виде функцию можно описать следующим шаблоном:

<возвращаемый тип> <имя функции>(<тип1> <арг1>, <тип1> <арг2>, ...) {
    <тело функции>
}

Функция имеет свою локальную область видимости – переменные, определённые внутри неё недоступны снаружи. По факту они создаются на стеке и после выхода из функции стек откатится к состоянию до попадания в функцию – данные станут недоступны (разве что не найдётся какой-то обходной путь – хак).

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

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

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

Что такое int?

Вернёмся к нашему "хитрому" типу данных. Типы данных во многих существующих языках – набор функций и ограничений на хранимую в переменной информацию. В Си же это размер байт, выделенных на переменную (этакое ограничение) + его представление (удобство, "синтаксический сахар" для работы с байтами).

В чём же хитрость?

Начнём с того, что int – знаковое целое – то есть туда можно положить целое число: хоть отрицательное, хоть положительное. Но не очень большое.

Размер int зависит то платформы. Посмотреть размер типа или переменной можно функцией sizeof:

    int main() {
        return sizeof(int);
    }

Собираем:

    clang int_size.c -o ./int_size

Запускаем:

falseecho $?
    4

– видим, что в нашем случае int занимает 4 байта.

А что, если мы туда положим что-то большее?

    int main(void) {
        int num = 0xffffffff;
        return num + 12;
    }

Переполнение:

echo $?
    11

На самом деле 0xffffffff – не самый большой int, самый большой – 0x7FFFFFFF из-за устройства представления байтов знаковым целым числом. В диапозоне [8000000, 0xffffffff] - отрицательные числа.

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

Однако, 0xffffffff прекрасно справляется с демонстрацией переполнения 0xffffffff + 12 = 11.

Ответ на загадку

Так что, если взять 0xffffffff и прибавить к нему 1... И вернуть это... В общем, это явно не было нулём, но стало! Более того: код возврата вообще ограничен беззнаковым байтом, так что каждое число, кратное 256 – также окажется истиной! И да, это уже вопросы к самому коду.