web-dev-qa-db-fra.com

Quand utiliser des pointeurs en C # /. NET?

Je sais que C # donne au programmeur la possibilité d'accéder, d'utiliser des pointeurs dans un contexte dangereux. Mais quand est-ce nécessaire?

Dans quelles circonstances, l'utilisation de pointeurs devient inévitable?

Est-ce uniquement pour des raisons de performances?

Pourquoi C # expose-t-il également cette fonctionnalité dans un contexte dangereux et en supprime-t-il tous les avantages gérés? Est-il possible d'avoir théoriquement des pointeurs d'utilisation sans perdre aucun avantage de l'environnement managé?

66
Joan Venge

Quand est-ce nécessaire? Dans quelles circonstances l'utilisation de pointeurs devient-elle inévitable?

Lorsque le coût net d'une solution gérée et sûre est inacceptable mais que le coût net d'une solution non sûre est acceptable. Vous pouvez déterminer le coût net ou l'avantage net en soustrayant le total des avantages du coût total. Les avantages d'une solution dangereuse sont des choses comme "pas de temps perdu sur des vérifications d'exécution inutiles pour garantir l'exactitude"; les coûts sont (1) d'avoir à écrire du code qui est sûr, même avec le système de sécurité géré désactivé, et (2) d'avoir à faire en sorte de rendre potentiellement le garbage collector moins efficace, car il ne peut pas déplacer la mémoire qui a un pointeur non géré dans il.

Ou, si vous êtes la personne qui écrit la couche de triage.

Est-ce uniquement pour des raisons de performances?

Il semble pervers d'utiliser des pointeurs dans un langage géré pour des raisons autres que les performances.

Vous pouvez utiliser les méthodes de la classe Marshal pour gérer l'interopérabilité avec du code non managé dans la grande majorité des cas. (Il peut y avoir quelques cas où il est difficile, voire impossible, d'utiliser le matériel de triage pour résoudre un problème d'interopérabilité, mais je n'en connais aucun.)

Bien sûr, comme je l'ai dit, si vous êtes la personne qui écrit la classe Marshal, vous ne pouvez évidemment pas tiliser la couche de triage pour résoudre votre problème. Dans ce cas, vous devez l'implémenter à l'aide de pointeurs.

Pourquoi C # expose-t-il cette fonctionnalité dans un contexte dangereux et en supprime-t-il tous les avantages gérés?

Ces avantages gérés s'accompagnent de coûts de performance. Par exemple, chaque fois que vous demandez à un tableau son dixième élément, le runtime doit faire une vérification pour voir s'il y a un dixième élément et lever une exception s'il n'y en a pas. Avec les pointeurs, le coût d'exécution est éliminé.

Le coût correspondant pour le développeur est que si vous le faites mal, vous devez faire face à des bogues de corruption de mémoire qui formate votre disque dur et plante votre processus une heure plus tard plutôt que de traiter une exception propre et agréable au point de l'erreur.

Est-il possible d'utiliser des pointeurs sans perdre aucun avantage de l'environnement géré, théoriquement?

Par "avantages", je suppose que vous entendez des avantages comme la collecte des ordures, la sécurité des types et l'intégrité référentielle. Ainsi, votre question est essentiellement "est-il théoriquement possible de désactiver le système de sécurité tout en bénéficiant des avantages du système de sécurité activé?" Non, ce n'est clairement pas le cas. Si vous désactivez ce système de sécurité parce que vous n'aimez pas son coût, vous n'en profitez pas!

84
Eric Lippert

Les pointeurs sont une contradiction inhérente à l'environnement géré et récupéré.
Une fois que vous commencez à jouer avec des pointeurs bruts, le GC n'a aucune idée de ce qui se passe.

Plus précisément, il ne peut pas dire si les objets sont accessibles, car il ne sait pas où se trouvent vos pointeurs.
Il ne peut pas non plus déplacer des objets dans la mémoire, car cela briserait vos pointeurs.

Tout cela serait résolu par des pointeurs suivis par GC; c'est ce que sont les références.

Vous ne devez utiliser des pointeurs que dans des scénarios d'interopérabilité avancés compliqués ou pour une optimisation hautement sophistiquée.
Si vous devez demander, vous ne devriez probablement pas.

17
SLaks

Le GC peut déplacer des références; l'utilisation non sécurisée maintient un objet hors du contrôle du GC et évite cela. "Fixe" épingle un objet, mais laisse le GC gérer la mémoire.

Par définition, si vous avez un pointeur sur l'adresse d'un objet et que le GC le déplace, votre pointeur n'est plus valide.

Pour savoir pourquoi vous avez besoin de pointeurs: la raison principale est de travailler avec des DLL non gérées, par exemple ceux écrits en C++

Notez également que lorsque vous épinglez des variables et utilisez des pointeurs, vous êtes plus sensible à la fragmentation du segment de mémoire.


Modifier

Vous avez abordé le problème principal du code managé par rapport au code non managé ... comment la mémoire est-elle libérée?

Vous pouvez mélanger le code pour les performances comme vous le décrivez, vous ne pouvez tout simplement pas franchir les limites gérées/non gérées avec des pointeurs (c'est-à-dire que vous ne pouvez pas utiliser de pointeurs en dehors du contexte `` dangereux '').

Quant à la façon dont ils sont nettoyés ... Vous devez gérer votre propre mémoire; les objets vers lesquels vos pointeurs pointent ont été créés/alloués (généralement dans la DLL C++) en utilisant (espérons-le) CoTaskMemAlloc(), et vous devez libérer cette mémoire de la même manière, en appelant CoTaskMemFree(), ou vous aurez une fuite de mémoire. Notez que seule la mémoire allouée avec CoTaskMemAlloc() peut être libérée avec CoTaskMemFree().

L'autre alternative consiste à exposer une méthode de votre DLL C++ native qui prend un pointeur et le libère ... cela permet au DLL de décider comment libérer la mémoire, ce qui fonctionne mieux s'il en utilise autre méthode pour allouer de la mémoire. La plupart des DLL natives avec lesquelles vous travaillez sont des DLL tierces que vous ne pouvez pas modifier, et elles n'ont généralement pas (comme je l'ai vu) de telles fonctions à appeler.

Un exemple de libération de mémoire, tiré de ici :

string[] array = new string[2];
array[0] = "hello";
array[1] = "world";
IntPtr ptr = test(array);
string result = Marshal.PtrToStringAuto(ptr);
Marshal.FreeCoTaskMem(ptr);
System.Console.WriteLine(result);


Encore du matériel de lecture:

mémoire de désallocation C # référencée par IntPtr La deuxième réponse vers le bas explique les différentes méthodes d'allocation/désallocation

Comment libérer IntPtr en C #? Renforce la nécessité de désallouer de la même manière que la mémoire a été allouée

http://msdn.Microsoft.com/en-us/library/aa366533%28VS.85%29.aspx Documentation MSDN officielle sur les différentes façons d'allouer et de désallouer la mémoire.

En bref ... vous devez savoir comment la mémoire a été allouée pour la libérer.


Modifier Si je comprends bien votre question, la réponse courte est oui, vous pouvez transmettre les données à des pointeurs non gérés, travailler avec dans un contexte dangereux, et disposer des données une fois que vous quittez le contexte dangereux.

La clé est que vous devez épingler l'objet géré auquel vous faites référence avec un bloc fixed. Cela empêche la mémoire à laquelle vous faites référence d'être déplacée par le GC dans le bloc unsafe. Il y a un certain nombre de subtilités impliquées ici, par exemple vous ne pouvez pas réaffecter un pointeur initialisé dans un bloc fixe ... vous devriez lire les instructions dangereuses et fixes si vous êtes vraiment prêt à gérer votre propre code.

Cela dit, les avantages de la gestion de vos propres objets et de l'utilisation de pointeurs de la manière que vous décrivez peuvent ne pas vous offrir autant d'augmentation de performances que vous ne le pensez. Pourquoi ne pas:

  1. C # est très optimisé et très rapide
  2. Votre code de pointeur est toujours généré en tant qu'IL, qui doit être jitted (auquel point d'autres optimisations entrent en jeu)
  3. Vous n'éteignez pas le Garbage Collector ... vous ne faites que garder les objets avec lesquels vous travaillez hors de la compétence du GC. Donc, toutes les 100 ms environ, le GC encore interrompt votre code et exécute ses fonctions pour toutes les autres variables de votre code managé.

HTH,
James

5
James King

Les raisons les plus courantes d'utiliser explicitement des pointeurs en C #:

  • faire un travail de bas niveau (comme la manipulation de chaînes) qui est très sensible aux performances,
  • interface avec les API non managées.

La raison pour laquelle la syntaxe associée aux pointeurs a été supprimée de C # (selon mes connaissances et mon point de vue - Jon Skeet répondrait mieux à B-)) est qu'elle s'est avérée superflue dans la plupart des situations.

Du point de vue de la conception du langage, une fois que vous gérez la mémoire par un garbage collector, vous devez introduire des contraintes sévères sur ce qui est et ce qu'il n'est pas possible de faire avec les pointeurs. Par exemple, l'utilisation d'un pointeur pour pointer au milieu d'un objet peut causer de graves problèmes au CPG. Par conséquent, une fois les restrictions en place, vous pouvez simplement omettre la syntaxe supplémentaire et vous retrouver avec des références "automatiques".

De plus, l'approche ultra-bienveillante trouvée en C/C++ est une source commune d'erreurs. Pour la plupart des situations, où les micro-performances n'ont aucune importance, il est préférable d'offrir des règles plus strictes et de contraindre le développeur en faveur de moins de bugs qui seraient très difficiles à découvrir. Ainsi, pour les applications commerciales courantes, les environnements dits "gérés" comme .NET et Java sont mieux adaptés que les langages qui supposent fonctionner contre la machine à nu).

3
Ondrej Tucny

Supposons que vous souhaitiez communiquer entre 2 applications en utilisant IPC (mémoire partagée), vous pouvez ensuite rassembler les données en mémoire et transmettre ce pointeur de données à l'autre application via la messagerie Windows ou quelque chose. À la réception de l'application, vous peut récupérer des données.

Utile également en cas de transfert de données de .NET vers des applications VB6 héritées dans lesquelles vous allez rassembler les données en mémoire, passer le pointeur vers l'application VB6 à l'aide de Win msging, utiliser VB6 copymemory () pour récupérer les données de l'espace mémoire géré vers la mémoire non gérée des applications VB6 espace..

2
variable