Передача значения по указателю на функцию. Должны ли мы создать копию переменной внутри функции?
У нас есть две простые функции.
#include <stdio.h>
/* first approach */
int power1(int *ptr)
{
return *ptr * *ptr;
}
/* second approach */
int power2(int *ptr)
{
int tmp = *ptr;
return tmp*tmp;
}
int main()
{
int val = 5;
printf("%d\n", power1(&val));
printf("%d\n", power2(&val));
return 0;
}
Какой из них лучше? power1 немного быстрее, но я слышал, что power2 больше безопасности. Не помню почему? Насколько я помню, есть один случай, когда power1(первый подход) имеет узкое место. Не могли бы вы объяснить это? Используют ли системы, важные для безопасности, второй подход?
4 ответа
Нет ничего хорошего Вы хотите это:
#include <stdio.h>
/* second approach */
int power(int operand)
{
return operand*operand;
}
int main(void)
{
int val = 5;
printf("%d\n", power(val));
}
Теперь о ваших двух подходах:
power2
ни в коем случае не "безопаснее", чем power1
,
КСТАТИ:
Правильный способ объявить main
является int main(void)
и return 0;
в конце main
не нужно, если main
не содержит return
Заявление, есть неявное return 0;
в конце main
,
Какой из них лучше?
Они одинаково хороши / плохи
power1 немного быстрее
Если вы компилируете без какой-либо оптимизации, тогда "да, power1 может быть немного быстрее", но как только вы включите оптимизацию компилятора, они будут (для любого приличного компилятора) равны.
но я слышал, что power2 больше безопасности
Это неверно. Использование переменных из списка аргументов так же безопасно, как и использование локальных переменных.
Используют ли системы, важные для безопасности, второй подход?
Нет причин для этого. Однако использование указателя запрещено в некоторых критических для безопасности системах. В вашем конкретном случае было бы лучше передать целое число напрямую, а не указатель.
Другая вещь, связанная с "безопасностью" - это целочисленное переполнение. Ваш код не защищает от целочисленного переполнения, а целочисленное переполнение - неопределенное поведение. Так что это может быть что-то, чтобы рассмотреть для критических систем безопасности.
Хотел бы я знать, что здесь подразумевается под "безопасностью" (я видел ваш комментарий, что вы получили это из интервью, и что интервьюер не объяснил, что он имел в виду под этим).
Есть только 4 причины, по которым функция должна получить указатель в качестве параметра:
- Функция предназначена для обновления параметра;
- Параметр является выражением массива, которое автоматически преобразуется в выражение указателя при передаче в качестве аргумента функции;
- Параметр очень большой
struct
или аналогичный тип агрегата, и создание локальной копии считается слишком дорогим; - Параметр был создан с помощью
malloc
,calloc
, или жеrealloc
,
Ничто из этого не должно относиться к фрагментам, которые вы разместили. Самый безопасный вариант для них - вообще не использовать указатель.
Одним из "небезопасных" аспектов использования указателя является то, что вы можете использовать вход только для чтения, но, поскольку вы получили указатель, вы можете изменить ввод. В этих случаях вы хотите const
-квалифицировать этот параметр:
void foo ( const char *str ) // we cannot modify what str points to
{
...
}
Другим "небезопасным" аспектом использования указателя является случайное (или намеренное) обновление самого значения указателя для доступа к памяти, которую вы не должны:
while ( *ptr )
do_something_with( ptr++ );
Вы можете смягчить это, объявив указатель как const
:
void bar( int * const ptr ) // we cannot update the value in ptr
Это не мешает вам использовать []
подстрочный оператор, хотя:
while( ptr[i] )
do_something_with( ptr[i++] );
Теперь, если ваш интервьюер думал о нескольких потоках или какой-то проблеме на уровне машины в отношении прерываний или нестабильности, тогда, возможно, у него есть точка зрения - если есть что-то, что может изменить вещь ptr
указывает на текущий поток управления выполнением, тогда да, второй метод в этом отношении "безопаснее" (значение, на которое указывает указатель, не изменится в середине вычисления).
Однако, если код многопоточный и ptr
могут быть изменены в разных потоках, доступ к ним должен быть синхронизирован через мьютекс или что-то еще. Если ptr
может быть обновлен вне контроля вашей программы, он должен был быть объявлен volatile
:
int power1( volatile int *ptr ) { ... }
int power2( volatile int *ptr ) { ... }
В power1
будет разыменование 2 раза - будет 2 просмотра памяти, связанных с разыменованием.
В power2
Разыменование будет, но только один раз. Только в заявлении int tmp = *ptr;
,
Так, power1
может быть неэффективным, если смотреть с точки зрения скорости.