Найти в Дзене
ZDG

Работа с критикой, часть 1

Когда-то я написал статью про программирование игры "5 букв": И вот в комментариях появился пользователь, который с уверенным видом начал раздавать поучения: это плохо, то плохо, это не так и т.д. Что ж, он достаточно долго смотрел в бездну. Кстати, тут не помешает почитать ещё вот эту статью: Итак, в чём суть. Функция в языке C не может возвращать массив фиксированной длины как копию. То есть нельзя написать такую функцию, которая имела бы тип массива с длиной, предположительно вот так: На что следует вот такой комментарий: Естественно, без каких-либо доказательств. Но есть один ценный совет: Давайте же ему последуем. А вот и пункт 6.9.1 стандарта, про функции: Нет, не может функция возвращать массивы. Я делаю вместо массива структуру, потому что структуру функция вернуть может: Очевидно, разницы с массивом у этой структуры нет никакой, она эквивалентна массиву, но вот массив вернуть нельзя, а структуру можно. Это связано с обратной совместимостью версий языка C. Если вкратце, раньше

Когда-то я написал статью про программирование игры "5 букв":

И вот в комментариях появился пользователь, который с уверенным видом начал раздавать поучения: это плохо, то плохо, это не так и т.д.

Что ж, он достаточно долго смотрел в бездну. Кстати, тут не помешает почитать ещё вот эту статью:

Итак, в чём суть.

Функция в языке C не может возвращать массив фиксированной длины как копию. То есть нельзя написать такую функцию, которая имела бы тип массива с длиной, предположительно вот так:

На что следует вот такой комментарий:

-2

Естественно, без каких-либо доказательств. Но есть один ценный совет:

-3

Давайте же ему последуем. А вот и пункт 6.9.1 стандарта, про функции:

www.open-std.org
-4

Нет, не может функция возвращать массивы.

Я делаю вместо массива структуру, потому что структуру функция вернуть может:

-5

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

Далее следует такой комментарий:

-6

Но ведь я именно об этом и написал в самой статье:

-7

То есть смотрите, я в курсе, что в C принято передавать в функцию указатель на буфер и модицировать буфер по указателю. Я вот прямо абсолютно в курсе этого, я же про это и пишу. Но я так делать не хочу. Я делаю осознанный отказ от этого. У меня есть причины. Я хочу, чтобы код был правильнее. Я это объяснил. Но тем не менее, надо меня поучить, что проще будет передавать указатель. Спасибо, а я-то не знал!

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

Функции, которые модифицируют свои аргументы – самые плохие и опасные функции, которые только могут быть. Они создают массу побочных эффектов. В них нет ничего хорошего. Ну и что, что в языке C так делается? Язык C прекрасен, но это не значит, что так надо делать всегда. Так надо делать, когда нет никакого другого выхода, а у меня он очевидно есть. Моя функция возвращает нормальный результат, как и положено функции.

Далее, рассмотрим вопрос эффективности. Ведь комментатор написал, "сначала всё пишется в структуру функции, а потом копируется при возврате".

Да, с наивной точки зрения так и происходит.

-8

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

-9

Неужели компилятор gcc сможет это понять? Я сделаю функцию, которая будет возвращать массив из букв H, e, l, l, o:

-10

И давайте посмотрим ассемблерный код функции:

-11

Легко видеть, где происходит заполнение массива ASCII-кодами 72, 101, 108, 108, 111. Какую область памяти заполняет функция? Это адрес, который хранится в регистре rax, а туда он попадает из регистра rcx.

Теперь смотрим функцию main():

-12

А вот функция main() записывает в регистр rcx адрес переменной word, это смещение -10[rbp] на стеке. И вызывает get_word()!

То есть что произошло по факту: по факту функция main() передала в функцию get_word() УКАЗАТЕЛЬ на переменную word, и функция get_word() записала данные прямо по этому указателю!

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

Но комментатору лучше знать.

Это первая часть работы с критикой. А вот и вторая:

Наука
7 млн интересуются