Передача значения по указателю на функцию. Должны ли мы создать копию переменной внутри функции?

У нас есть две простые функции.

#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 причины, по которым функция должна получить указатель в качестве параметра:

  1. Функция предназначена для обновления параметра;
  2. Параметр является выражением массива, которое автоматически преобразуется в выражение указателя при передаче в качестве аргумента функции;
  3. Параметр очень большой struct или аналогичный тип агрегата, и создание локальной копии считается слишком дорогим;
  4. Параметр был создан с помощью 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 может быть неэффективным, если смотреть с точки зрения скорости.

Другие вопросы по тегам