2
votes

Passer la valeur par le pointeur à la fonction. Devons-nous créer une copie de la variable à l'intérieur de la fonction?

Nous avons deux fonctions simples.

#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;
}

Lequel est le meilleur? power1 est un peu plus rapide, mais j'ai entendu dire que power2 est plus de sécurité. Je ne me souviens pas pourquoi? Autant que je me souvienne, il y a un cas où power1 (première approche) a un goulot d'étranglement. Pouvez-vous l'expliquer? Les systèmes critiques pour la sécurité utilisent-ils une deuxième approche?


3 commentaires

power1 est un peu plus rapide - Si vous prétendez cela, où est votre code qui teste cela? Les compilateurs no-nonsense devraient générer le même code pour les deux fonctions.


Quelle est la raison pour laquelle vous voulez passer des pointeurs? Dans ce cas précis, c'est inutile (jeu de mots non prévu).


la seule raison de passer le pointeur est d'obtenir val = val * val sans utiliser plus de variables dans votre code principal. Dans ce cas, vous pouvez utiliser void power (int * a) {* a = * a * * a;}


4 Réponses :


6
votes

Aucun n'est bon. Vous voulez ceci:

#include <stdio.h>

/* second approach */
int power(int operand)
{
    return operand*operand;    
}

int main(void)
{
    int val = 5;    
    printf("%d\n", power(val));
}

Concernant maintenant vos deux approches:

power2 n'est en aucun cas "plus sûr" que power1 code>.

BTW:

Une manière correcte de déclarer main est int main (void) et le return 0; à la fin de main n'est pas nécessaire, si main ne contient pas d'instruction return , il y a une implicite return 0; à la fin de main.


7 commentaires

Mes exemples proviennent d'interviews :) Je ne sais toujours pas pourquoi les personnes interrogées ont dit que power2 est meilleur (plus de sécurité).


@ user3483899 très bonne question. BTW "plus sûr" en termes de quoi?


Je ne suis pas sûr, ils voulaient probablement interrompre. J'ai oublié d'écrire cette valeur (où les points ptr) peuvent être modifiés en interruption.


@ user3483899 cela pourrait être une raison, mais comme int * ptr n'est pas déclaré volatile , le compilateur peut générer le même code pour les deux versions.


@ user3483899 vérifiez ici , les deux versions génèrent le même code, mais la version avec volatile génère un code différent.


@ user3483899 Il y a beaucoup de mauvais intervieweurs là-bas.


@ user3483899 Il y a beaucoup de mauvais intervieweurs là-bas.



2
votes

Lequel est le meilleur?

Ils sont tout aussi bons / mauvais

power1 est un peu plus rapide

Si vous compilez sans aucune optimisation, alors "oui, power1 peut être un peu plus rapide" mais dès que vous activez l'optimisation du compilateur, elles seront (pour tout compilateur décent) égales.

mais j'ai entendu dire que power2 est plus de sécurité

C'est faux. L'utilisation des variables de la liste d'arguments est tout aussi sûre que l'utilisation de variables locales.

Les systèmes critiques pour la sécurité utilisent-ils une deuxième approche?

Aucune raison de faire ça. Cependant, l'utilisation de pointeurs est interdite dans certains systèmes critiques pour la sécurité. Dans votre cas particulier, il serait préférable de passer directement l'entier au lieu de passer un pointeur .

Une autre chose liée à la "sécurité" est le débordement d'entiers. Votre code ne protège pas contre le débordement d'entier et le dépassement d'entier est un comportement non défini. Cela pourrait donc être quelque chose à considérer pour les systèmes critiques pour la sécurité.


0 commentaires

-1
votes

Dans power1 , il y aura un déréférencement 2 fois - il y aura 2 recherches de mémoire liées au déréférencement.

Dans power2 il y aura un déréférencement, mais une seule fois. Uniquement dans l'instruction int tmp = * ptr; .

Donc, power1 pourrait être inefficace si l'on regarde de cette façon en termes de vitesse.

Ceci est basé sur des hypothèses si les optimisations du compilateur sont désactivées.


3 commentaires

Vous ne pouvez vraiment pas faire ce genre d'hypothèses sur le code sans inspecter la sortie du compilateur. Il n'y a aucune exigence dans la langue que le code entraîne un nombre particulier de «recherches» de mémoire. Aussi, les caches.


La recherche de mémoire n'est pas une hypothèse. Je voulais dire ceci: lors du déréférencement d'un pointeur, vous prenez d'abord la valeur de la variable à partir de l'identifiant de la variable. Et puis ensuite vous prenez cette valeur comme adresse et allez à nouveau chercher la valeur à cette adresse


Il n'est pas nécessaire que ce code entraîne une recherche de mémoire. Un compilateur optimisant peut faire disparaître cela.



1
votes

J'aurais aimé savoir ce que «sécurité» est censé signifier ici (j'ai vu votre commentaire selon lequel vous avez obtenu ceci d'une interview, et que l'intervieweur n'a pas expliqué ce qu'il voulait dire par là).

Il n'y en a que 4 raisons pour lesquelles une fonction devrait recevoir un pointeur comme paramètre:

  1. La fonction est destinée à mettre à jour le paramètre;
  2. Le paramètre est une expression de tableau, qui est automatiquement convertie en une expression de pointeur lorsqu'elle est passée en tant qu'argument de fonction;
  3. Le paramètre est un très gros struct ou un type d'agrégat similaire, et la création d'une copie locale est jugée trop coûteuse;
  4. Le paramètre a été créé via malloc , calloc ou realloc .

Aucun de ces éléments ne doit s'appliquer aux extraits que vous avez publiés. L'option la plus "sûre" pour eux est de ne pas utiliser du tout de pointeur.

Un aspect "dangereux" de l'utilisation d'un pointeur est que vous pouvez avoir l'intention que l'entrée soit en lecture seule, mais parce que vous ' vous avez passé un pointeur, vous pouvez modifier l'entrée. Dans ces cas, vous voulez const -qualifier ce paramètre:

int power1( volatile int *ptr ) { ... }
int power2( volatile int *ptr ) { ... }

Un autre aspect "dangereux" de l'utilisation d'un pointeur est accidentellement (ou délibérément) vous ne devriez pas mettre à jour la valeur du pointeur lui-même pour accéder à la mémoire:

while( ptr[i] )
  do_something_with( ptr[i++] );

Vous pouvez atténuer cela en déclarant le pointeur comme const :

void bar( int * const ptr ) // we cannot update the value in ptr

Cela ne vous empêche pas d'utiliser l'opérateur d'indice [] , cependant:

while ( *ptr )
  do_something_with( ptr++ );

Maintenant, si votre intervieweur pensait à plusieurs threads ou à un problème au niveau de la machine en ce qui concerne les interruptions ou la volatilité, alors peut-être qu'il a raison - s'il y a quelque chose qui peut modifier la chose ptr pointe vers l'extérieur du thread courant du contrôle d'exécution, alors oui, la deuxième méthode est "plus sûre" à cet égard (la valeur pointée ne changera pas au milieu du calcul).

Cependant, si le code est multithread et que ptr peut être modifié dans différents threads, son accès doit être synchronisé via un mutex ou quelque chose. Si ptr peut être mis à jour en dehors du contrôle de votre programme, il devrait avoir été déclaré volatile:

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


0 commentaires