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

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

#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 ]

0 голосов
/ 21 февраля 2019

Хотелось бы знать, что здесь означает "безопасность" (я видел твой комментарий, что ты получил это из интервью, и что интервьюер не объяснил, что он имел в виду).

ТамЕсть только 4 причины, по которым функция должна получать указатель в качестве параметра:

  1. Функция предназначена для обновления параметра;
  2. Параметр является выражением массива, которое автоматически преобразуетсяк выражению указателя при передаче в качестве аргумента функции;
  3. Параметр имеет очень большой struct или аналогичный агрегатный тип, и создание локальной копии считается слишком дорогим;
  4. Параметр былсозданный с помощью malloc, calloc или realloc.

Ни один из них не должен применяться к опубликованным фрагментам.«Самый безопасный» вариант для них - вообще не использовать указатель.

Один «небезопасный» аспект использования указателя заключается в том, что вы можете использовать вход только для чтения, но посколькубыл передан указатель, вы можете изменить ввод.В этих случаях вы хотите const -квалифицировать этот параметр:

void foo ( const char *str ) // we cannot modify what str points to
{
  ...
}

Другим «небезопасным» аспектом использования указателя является случайное (или умышленное) обновление самого значения указателя для доступа к памяти, которую вы не должны 't:

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 ) { ... }
0 голосов
/ 21 февраля 2019

Какой из них лучше?

Они одинаково хороши / плохи

power1 немного быстрее

Если вы компилируете без какой-либо оптимизации, то «да, power1 может быть немного быстрее», но как только вы включите оптимизацию компилятора, они (для любого приличного компилятора) будут равны.

но я слышал, что power2 больше безопасности

Это неправильно.Использование переменных из списка аргументов так же безопасно, как и использование локальных переменных.

Использует ли критический для безопасности системы второй подход?

Нет причин для этого.Однако использование указателя запрещено в некоторых критических для безопасности системах.В вашем конкретном случае было бы лучше передать целое число напрямую, а не указатель.

Еще одна вещь, связанная с «безопасностью», - это переполнение целых чисел.Ваш код не защищает от целочисленного переполнения, а целочисленное переполнение - неопределенное поведение.Так что это может быть что-то, чтобы рассмотреть для критических систем безопасности.

0 голосов
/ 21 февраля 2019

В power1 будет разыменование 2 раза - будет 2 просмотра памяти, связанных с разыменованием.

В power2 Будет разыменование, но только один раз.Только в утверждении int tmp = *ptr;.

Итак, power1 может быть неэффективным, если смотреть таким образом с точки зрения скорости.

0 голосов
/ 21 февраля 2019

Нет, это хорошо.Вы хотите это:

#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.

BTW:

Правильный способ объявить main - это int main(void), а return 0; в конце main не требуется, если main не содержит оператора return, существует неявныйreturn 0; в конце main.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...