Будет ли экономиться размер файла если в параметры функции передавать не значения а указатели?
Например есть 2-е функции
void func1(int ValueA, int ValueB)
{
int a = ValueA + ValueA;
int b = ValueB + ValueB;
}
void func2(int* PtrValueA, int* PtrValueB)
{
int a = *PtrValueA + *PtrValueA;
int b = *PtrValueB + *PtrValueB;
}
int main()
{
int a = 10, b = 20;
func1(a, b);
func2(&a, &b);
}
Если я правильно понял то если в функцию передавать значения а не указатели как в func1 то при компиляции для параметров функции func1 будет выделена доп. память т.е. будет выделена память на переменные a, b в функции main + будет выделена память для параметров func1. Из этого у меня и возник вопрос с точки зрения размера файла не будет ли выгоднее передавать указатели?
Ответы (2 шт):
Память для переменных может выделятся в стеке (сразу выделенный участок памяти при старте программы) а не в файле. Размер файла не будет меняться, так как место памяти, где хранится программа и локальные переменные функции - разные.
По-умолчанию в Си переменные имеют отметку автоматического хранения :
auto int a = ..
это значит, что переменные могут храниться или в стеке или в регистрах процессора или в самих командах mov %%rax, 10.
При передачи указателями void func2(int* PtrValueA, int* PtrValueB) компилятору уменьшается возможность оптимизировать и код функции должен брать значения по выданному указателю и так-же для передачи аргументов, должна быть выделена память (часть стека).
Параметры функций также имеют по-умолчанию автоматическое место хранения и передаются копии. Никакой разницы для аргументов не будет, будь они числа или указатели.
Ответ :
Размер программы не имеет отношения к локальных переменным.
При передачи аргументов копией func1 может быть оптимизирована память в стеке.
Если аргументы не помещаются в размеры регистров процессора, тогда создастся копия в стеке.
Обычно простые переменные передают копией (с надеждой на оптимизацию), а структуры/объекты указателем/ссылкой.
Тоже отвечу, но немного с другой колокольни. Если речь идёт об экономии размера скомпилированного "экзешника", то вопрос смысла не имеет. В исполняемых файлах присутствуют выравнивания, так что одним байтом больше, одним меньше - разницы не будет никакой. А вот что действительно имеет значение, так это производительность кода. И да, передача параметров типа int по значению будет эффективнее, чем передача указателей, т.к. в последнем случае понадобятся дополнительные операции по извлечению этих самых значений из указателей (разыменование). Так что если функция оперирует значениями передаваемых простых переменных (имеющих тип int, float, и т.д.), то и передавать следует значения этих переменных, а не указатели.
Однако для ясности разберём ваш пример. Дизассемблируем ваш код и посмотрим, сколько машинных инструкций и какие содержат функции func1() и func2(). Пугаться ассемблера тут не стоит, ибо нас интересует в данный момент только количество сгенерированных компилятором инструкций, а их смысл я дополнительно укажу в комментариях. И так, вот он, вид функции func1() после компиляции:
func1 proc near
retn
func1 endp
Теперь функция func2():
func2 proc near
retn
func2 endp
Как видим, обе функции содержат только одну инструкцию - retn (нетрудно догадаться, что это оператор return;). Обе функции ничего не делают и занимают в коде ровно 1 байт! Как так получилось? А просто ваши функции действительно ничего не делают. Да, там присутствуют какие-то арифметические вычисления над локальными переменными, однако результат никуда не сохраняется, локальные переменные a и b после возврата уничтожаются. Компилятор увидел это, и весь ненужный код удалил.
Так что сделаем так, чтобы обе функции сохраняли результат своих вычислений, ну скажем, в некоторую дополнительную структуру, указатель на которую (именно указатель, а не значение), передадим через третий параметр. Выглядеть это будет так:
struct RESULT
{
int a;
int b;
};
void func1(int ValueA, int ValueB, RESULT* pRes)
{
pRes->a = ValueA + ValueA;
pRes->b = ValueB + ValueB;
}
void func2(int* PtrValueA, int* PtrValueB, RESULT* pRes)
{
pRes->a = *PtrValueA + *PtrValueA;
pRes->b = *PtrValueB + *PtrValueB;
}
int main()
{
int a = 10, b = 20;
RESULT res;
func1(a, b, &res);
func2(&a, &b, &res);
return 0;
}
Теперь первая функция в скомпилированном коде будет выглядеть так (смысл каждой инструкции прокомментирован):
func1 proc near
ValueA = dword ptr 4
ValueB = dword ptr 8
pRes = dword ptr 0Ch
mov eax, [esp+ValueA] ; берём ValueA
mov edx, [esp+pRes] ; берём указатель pRes
add eax, eax ; складываем ValueA + ValueA
mov [edx], eax ; записываем результат в pRes->a
mov eax, [esp+ValueB] ; берём ValueB
add eax, eax ; складываем ValueB + ValueB
mov [edx+4], eax ; записываем результат в pRes->b
retn ; выходим из функции
func1 endp
А вторая так:
func2 proc near
PtrValueA = dword ptr 4
PtrValueB = dword ptr 8
pRes = dword ptr 0Ch
mov eax, [esp+PtrValueA] ; берём указатель PtrValueA
mov edx, [esp+pRes] ; берём указатель pRes
mov eax, [eax] ; извлекаем значение *PtrValueA
add eax, eax ; складываем *PtrValueA + *PtrValueA
mov [edx], eax ; записываем результат в pRes->a
mov eax, [esp+PtrValueB] ; берём указатель PtrValueB
mov eax, [eax] ; извлекаем значение *PtrValueB
add eax, eax ; складываем *PtrValueB + *PtrValueB
mov [edx+4], eax ; записываем результат в pRes->b
retn ; выходим из функции
func2 endp
И того, функция func1() содержит 8 команд процессора, а функция func2() - уже 10 комманд, т.к. две дополнительные инструкции приходятся на разыменование указателей. Так что первая функция хоть чуть-чуть, но производительнее второй. А что касается их размера в байтах, то он неважен. ОК, вторая функция на 4 байта длинней первой - это фитюлька, которую вообще не стоит учитывать! Тем более, что из-за выравнивания на размер кода в целом эта разница никак не скажется.
Ещё раз: производительность - вот, на что надо ориентироваться при написании кода, а не на размер (а если всё таки хочется уменьшить размер исполняемого файла, то для этого существуют специальные архиваторы-упаковщики, на подобие UPX и ему подобные).
И да, выше приведённый разбор справедлив, если в качестве параметров передаются простые типы данных, такие как int, float, double - всё, что до восьми байт длинной. Большие же массивы и структуры целесообразно передавать через указатель или ссылку, в противном случае процесс передачи таких больших значений будет занимать несравненно больше времени, чем обращения по указателю.
Надеюсь, я вам подкинул пищу для размышлений))