7
votes

Comment programmez-vous en toute sécurité en dehors d'un environnement de code géré?

Si vous êtes une personne qui est des programmes en C ou C ++, sans les avantages de la gestion de la mémoire de la gestion de la mémoire, de type vérification ou de dépassement tampon, à l'aide de l'arithmétique du pointeur, comment assurez-vous que vos programmes sont en sécurité? Utilisez-vous beaucoup de tests unitaires ou êtes-vous juste un codeur prudent? Avez-vous d'autres méthodes?


8 commentaires

Je ne m'attends pas vraiment à une myriade de réponses. Je soupçonne que très peu de techniques de base utilisent la plupart des gens.


C / C ++ a une gestion de la mémoire (pointeur intelligent). C'est une langue fortement typée. La protection tampon est facultative (utilisation à () plutôt que opérateur []). Donc, ce n'est pas comme si nous utilisions des couteaux de pierre ici.


@Martin Je ne suis généralement pas celui qui pénètre dans l'ensemble "Je vais me plaindre à quelqu'un qui a utilisé" C / C ++ "", mais je pense que cela aurait valable de simplement dire C ++ dans cette déclaration. C n'a pas de méthodes standard pour la protection de la mémoire ou des limites de mémoire tampon.


@Falaina - sauf si vous voulez dire d'encapsulation de nasties ou à l'aide de bibliothèques préexistantes pour vous faire pour vous, non plus C ++ - et tous les deux sont viables dans C. Par exemple, je suis sûr que je me souviens d'au moins un c Bibliothèque qui a fait à peu près les flux de bibliothèque standard C ++ et les chaînes de la bibliothèque standard (le problème pertinent étant la prévention du dépassement tampon à l'aide de tampons redimensionnables), à l'aide d'un système de poignée pour masquer les internes.


Sans ces avantages modernes, il est presque impossible d'écrire des programmes de travail. C'est pourquoi les systèmes d'exploitation écrasent tout le T


@Falaina - Ce n'est pas un tout ou rien. Considérez la fonction STD C Strcpy, par exemple, qui n'est pas des bornes vérifiées. Nous avons maintenant aussi Strncpy que est cochée sur les limites , et est donc recommandée dans à peu près tous les cas que vous auriez pu utiliser Strcpy avant. Il en va de même pour la plupart des fonctions de manipulation de chaîne. Donc, dans de nombreux domaines (nullement tous), la protection contre les dépassements de tampon dans le nouveau code C est la matière d'éducation et de choix. En général C ++ prend des choses, bien sûr, et si vous suivez que les idiomes ne sont pas significativement plus «dangereux» qu'un langage mignon.


@Phil: Je ne recommanderais pas à peu près Strncpy (). C'est bizarre et non aussi sûr que possible.


Détendez-vous: il me semble que «N'-Fonctions (et similaires) ajoutez une complexité au code. Vous avez besoin du code pour vérifier la taille des tampons si vous utilisez la variante 'N'-N'-NO. Utilisez std :: chaîne personnes. Et si vous utilisez toujours C au lieu de C ++, ne le faites pas.


9 Réponses :


24
votes

Tout ce qui précède. J'utilise:

  1. beaucoup de prudence
  2. Pointeurs intelligents autant que possible
  3. Les structures de données testées, beaucoup de bibliothèque standard
  4. Tests unitaires tout le temps
  5. Outils de validation de la mémoire telles que MemValidator et Appverificateur
  6. Priez chaque nuit qu'il ne plante pas sur le site client.

    En fait, je suis juste exagérer. Ce n'est pas trop mauvais et il n'est en fait pas trop difficile pour garder le contrôle des ressources si vous structurez correctement votre code.

    Note intéressante. J'ai une application importante qui utilise DCOM et a des modules gérés et non gérés. Les modules non gérés sont généralement plus difficiles à déboguer pendant le développement, mais se produisent très bien sur le site du client en raison des nombreux tests qui l'exécutent. Les modules gérés souffrent parfois de mauvais code car le collecteur des ordures est tellement flexible, les programmeurs deviennent paresseux dans la vérification de l'utilisation des ressources.


9 commentaires

Les pointeurs intelligents sont un dont je n'ai pas entendu parler avant. Je vais devoir vérifier ça.


Je dois en utiliser beaucoup depuis que je suis en proie au code de l'héritage COM. Sans pointeurs intelligents, je perdrai une trace de toutes ces références et la mémoire d'hémorragie.


J'ai développé une allergie pour voir des pointeurs nus au code C ++. Si je vois un mon instinct, c'est de le terminer dans un pointeur intelligent, même si cela n'est pas nécessaire. L'instinct m'a bien servi - je ne me souviens pas d'un pointeur pendeux pour probablement dix ans ou plus.


Les pointeurs intelligents IMO ne sont qu'une mesure d'arrêt-gap. La collecte des ordures est la vraie affaire. Je pense qu'il est bien connu d'ici maintenant que les pointeurs intelligents (la variété refitée au moins et la variété Auto_PTR une utilisation limitée de toute façon) ne peut pas correspondre aux caractéristiques de performance d'un collecteur de déchets bien réglé. Mais là encore, cela vient d'une personne qui pense qu'il est temps de prendre sa retraite de C ++.


@ SDX2000: Je pense que les développeurs C ++ les plus expérimentés affirment que la collection des ordures est inefficace au mieux et une béquille au pire, par rapport à la bonne utilisation des pointeurs intelligents. Il existe des collectionneurs à déchets disponibles pour C ++, mais ils ne sont pas favorisés en raison de la mise en œuvre efficace et de la variété des implémentations de pointeur intelligentes disponibles. Évidemment, votre compréhension des pointeurs intelligents semble affecter votre opinion que je suggère de lire davantage sur la manière et quand quand les utiliser (car auto_ptr n'a pas une utilisation limitée, elle a une utilisation très précise (transfert de propriété) bien définie).


@ SDX2000: Le concept de retraite d'une langue est risible. Chaque langue est bonne pour résoudre des problèmes dans différents espaces d'application. C # / Java / C ++ / C a tous des domaines différents (mais qui se chevauchent) où ils brillent et d'autres zones où ils ne sont pas aussi utiles. Vous ne devez pas utiliser une langue car c'est celui que vous connaissez, vous devez utiliser une langue qui correspond le mieux au domaine du problème que vous essayez d'écrire un programme.


@Martin - merci pour vos aimables paroles de sagesse ... Opinion populaire, il semble rester divisé comme d'habitude, par exemple, voir Stackoverflow.com/questions/867114/... . Il n'y a aucune raison de croire que l'un des mécanismes de collecte des ordures est plus rapide / plus efficace que l'autre sans une évaluation quantitative. Et juste fyi ma compréhension des pointeurs intelligents n'est pas aussi superficielle que vous pouvez penser ... j'ai à certaines occasions écrites TR1 / Shared_Ptr comme des pointeurs intelligents et je suis bien conscient de leurs forces et de leurs faiblesses.


@Martin - En réponse à votre deuxième commentaire, vous avez raison, c'est vraiment risible. J'aurais dû être plus précis lorsque j'ai dit que C ++ devrait prendre sa retraite maintenant. Ce que je voulais dire était ... Il est grand temps maintenant que nous réévaluons la position de C ++ en tant qu'outil de résolution de problèmes génériques et que vous arrêtez l'utilisation dans les domaines qui sont mieux servis par d'autres langues modernes. Si vous avez déjà travaillé dans C #, vous saurez que C ++ est un pita. Je suis programmée en C ++ depuis 15 ans que mes côtelettes C ++ ne sont pas en question ici.


Il n'y a rien efficace sur les pointeurs intelligents. Comptage de référence (en supposant que c'est le type de pointeur intelligent que nous parlons) est ridiculement inefficace comparé à la GC Decent TO. Un bon programmeur C ++ devrait accepter ce fait. Les collectionneurs à ordures sont très efficaces, bien plus que le refCount primitif que nous utilisons en C ++. Les pointeurs intelligents ont d'autres qualités rachetées bien sûr, qu'un GC ne peut pas offrir. Mais la performance n'est pas parmi elle.



13
votes

Tout comme pertinent - comment vous vous assurez que vos fichiers et vos prises sont fermés, vos serrures sont libérées, Yada Yada. La mémoire n'est pas la seule ressource, et avec GC, vous perdez intrinsèquement fiable / en temps voulu.

ni gc ni non-gc ne sont automatiquement supérieurs. Chacun a des avantages, chacun a son prix et un bon programmeur doit pouvoir faire face à la fois avec les deux.

J'ai dit autant dans une réponse à Cette question .


8 commentaires

Il existe des techniques pour faire Raii dans des langues gérées: Levelodiindirection.com/journal/2009/9/24/... SVIGHTIDIVERSECK.COM/JOURNAL/2009/9/24/...


... et niveau de niveau de Journal / 2009/9 / 24 / ...


@Phil - lecture intéressante, mais bien sûr, quiconque pense "cela prouve C # et Java Beat C ++" devrait en fait lire ces liens. Si une idiome était une guérison magique, les idiomes pour assurer la suppression appropriée des objets alloués à l'écran en C ++ seraient également des guérisons magiques et nous ne verrions pas les ventilateurs de collecte des ordures qui se moquent de C ++.


Les sockets et les serrures de fichiers sont un hareng rouge. Il existe des modèles simples et bien établis pour ceux-ci dans des langues gérées. En C # c'est l'instruction "Utilisation", qui dispose automatiquement des ressources lorsqu'elles ne sont plus nécessaires.


@HARVEY - Ce n'est pas tous les socket ou chaque fichier ne vit que pour la vie d'un appel de fonction unique - et où ils le font, une variable locale C ++ utilisant Encapsulé Raii est plus propre et moins erronée que l'essai / enfin. Considérer par exemple Les fichiers sous-jacents aux documents de l'application GUI, que vous voudrez peut-être rester ouvert (par exemple pour le verrouillage). Vous pouvez avoir plusieurs objets de vue faisant référence à ce document. Déjà, vous traitez des problèmes liés à la fois au GC et à la RAII. Dans les deux cas, il existe des idiomes pour assurer une partie du travail réalisé, mais le programmeur doit appliquer ces idiomes correctement et assumer généralement la responsabilité.


Désolé - pas seulement essayer / enfin, mais utiliser aussi et d'autres idiomes. Les approches de la GC-Languages ​​ont besoin d'un idiome à répéter pour chaque utilisation d'une classe qui en a besoin, alors que c ++ encapsule le Raii dans la classe une fois et pour tous.


J'ajouterai également que l'essai / enfin ou tout ce qui peut être plus propre lorsque vous voudrez peut-être une simple variable locale, mais ce que vous avez obtenu est une référence à un objet alloué en tas d'une sorte d'usine. Nettoyant toujours si la mémoire est la seule ressource à libérer, bien sûr.


@ROBERT HARVEY: Il existe des modèles simples et bien établis pour la manipulation de la mémoire en standard C ++. De quoi te plains tu? Les langues font les choses différemment. Chaque approche est meilleure à certains égards et pire dans d'autres. Les deux travaux.



0
votes

C ++ a toutes les fonctionnalités que vous mentionnez.

Il y a une gestion de la mémoire. Vous pouvez utiliser des pointeurs intelligents pour un contrôle très précis. Ou il y a quelques collectionneurs à ordures disponibles bien qu'ils ne font pas partie de la norme (mais la plupart des situations intelligentes les pointeurs sont plus que suffisants).

C ++ est une langue fortement dactylographiée. Tout comme c #.

Nous utilisons des tampons. Vous pouvez choisir d'utiliser une version vérifiée des limites de l'interface. Mais si vous savez qu'il n'y a pas de problème, vous êtes libre d'utiliser la version non cochée de l'interface.

Comparer la méthode de () (vérifié) à l'opérateur [] (non coché).

Oui, nous utilisons des tests unitaires. Tout comme vous devriez utiliser en C #.

Oui, nous sommes des codeurs prudents. Tout comme si tu devrais être en C #. La seule différence est que les pièges sont différents dans les deux langues.


6 commentaires

Je n'ai pas vu la question "Le C ++ a-t-il les avantages modernes de la gestion de la mémoire" étant posé, mais "si vous programmez en C ++, sans les avantages modernes de la gestion de la mémoire, ..., comment Vous assurez que vos programmes sont en sécurité? "


Si je programme sans pointeur intelligent, c'est beaucoup plus difficile de s'assurer que mes programmes sont en sécurité. Je ne vois pas la pertinence, cependant. Si vous programmez en C # sans utiliser l'instruction "Utilisation" (quel IIRC est un ajout assez récent), comment assurez-vous que vos autres ressources sont correctement disposées?


Les indicateurs intelligents ne sont-ils pas adéquats dans les mêmes situations que VB6 et COMPT de référence COM était adéquat? C'est ce que Microsoft voulait améliorer quand ils ont choisi le style .NET de la collection de déchets.


@MarkJ: difficilement. Com Comptage de référence Placez la réponse de la réponse à l'utilisateur. Le pointeur intelligent comme GC met la responcifie sur le développeur du pointeur intelligent / gc. Fondamentalement, les pointeurs intelligents sont un grain de collection de déchets beaucoup plus fin, déterministe (contrairement à GC qui n'est pas déterministe).


@MarkJ: Dans Java GC ajoute tellement d'autres problèmes que lestructeurs (ou les finaliseurs sont pratiquement illicités) tandis que dans .net ils devaient ajouter le concept d'utilisation de la collecte des ordures utilisables. Donc, la vraie question est de savoir pourquoi vous pensez que l'utilisation de la cote "à l'aide de" est meilleure que "les pointeurs intelligents" lorsque "Utilisation" met la responcité à l'utilisateur de l'objet comme le comptage de référence COM.


Lisez ceci: Stackoverflow.com/Questtions/1064325/... Pour une description plus détaillée des pointeurs intelligents et ses avantages de GC.



16
votes

J'utilise des lots et beaucoup d'affirmations et construisez une version "débogage" et une version "version". Ma version de débogage fonctionne beaucoup plus lentement que ma version de libération, avec tous les chèques.

i courante fréquemment sous Valgrind , et mon code a des fuites de mémoire zéro. Zéro. Il est beaucoup plus facile de garder un programme sans fuites que de prendre un programme de buggy et de réparer toutes les fuites.

En outre, mon code compile sans avertissements, malgré le fait que j'ai le compilateur pour des avertissements supplémentaires. Parfois, les avertissements sont idiotes, mais parfois, ils font obstacle à un bug, et je le répare sans qu'il soit nécessaire de le trouver dans le débogueur.

J'écris pure C (je ne peux pas utiliser C ++ sur ce projet), mais je fais du C de manière très cohérente. J'ai des cours axés sur des objets, avec des constructeurs et des destructeurs; Je dois les appeler à la main, mais la cohérence aide. Et si j'oublie d'appeler un destructeur, Valgrind me frappe sur la tête jusqu'à ce que je le répare.

En plus du constructeur et du destructeur, j'écris une fonction de contrôle automatique qui examine l'objet et décide s'il est sain d'esprit ou non; Par exemple, si une poignée de fichier est nulle mais que les données de fichier associées ne sont pas zozées, cela indique une sorte d'erreur (la poignée a été classée ou que le fichier n'était pas ouvert, mais ces champs de l'objet ont des ordures. En outre, la plupart de mes objets ont un champ "signature" qui doit être défini sur une valeur spécifique (spécifique à chaque objet différent). Les fonctions qui utilisent des objets généralement affirment que les objets sont sains.

Chaque fois que je masloc () Quelques mémoire, ma fonction remplit la mémoire avec 0xdc valeurs. Une structure qui n'est pas entièrement initialisée devient évidente: les comptes sont trop gros, les pointeurs sont invalides ( 0xdcdcdcdc ), et lorsque je regarde la structure dans le débogueur, il est évident qu'il est incitacialisé. C'est beaucoup mieux que la mémoire de remplissage zéro lorsque vous appelez MALLOC () . (Bien sûr, le remplissage 0xdc est uniquement dans la construction de débogage; pas besoin de la construction de libération pour perdre ce temps.)

Chaque fois que je libère la mémoire, j'efface le pointeur. De cette façon, si j'ai un bogue stupide où le code tente d'utiliser un pointeur après la libération de sa mémoire, j'obtiens instantanément une exception null-pointer, qui me pointe directement au bug. Mes fonctions destructeurs ne prennent pas de pointeur sur un objet, ils prennent un pointeur sur un pointeur et encombrent le pointeur après la destruction de l'objet. De plus, les destructeurs essuent leurs objets avant de les libérer, de sorte que si une partie du code a une copie d'un pointeur et essaie d'utiliser un objet, la vérification de la santé mentale affirme instantanément.

Valgrind me dira si un code écrit la fin d'un tampon. Si je n'avais pas cela, j'aurais placé des valeurs "Canaries" après les extrémités des tampons et que la vérification de la santé mentale, testez-les. Ces valeurs canariales, telles que les valeurs de signature, constitueraient uniquement la version de débogage, de sorte que la version de libération n'aurait pas de bruit de mémoire.

J'ai une collection de tests unitaires, et lorsque je modifie les changements majeurs du code, il est très réconfortant d'exécuter les tests de l'unité et de faire confiance à ce que je n'ai pas enfreint les choses. Bien sûr, j'exécute les tests de l'unité sur la version de débogage ainsi que la version de version, de sorte que tous mes affirmations ont leur chance de trouver des problèmes.

Mettre toute cette structure en place était un peu d'effort supplémentaire, mais cela paye chaque jour. Et je me sens assez heureux quand une affirmation incendie et me pointe de me diriger vers un bug, au lieu de devoir exécuter le bogue dans le débogueur. À long terme, c'est juste moins de travail pour garder les choses propres tout le temps.

Enfin, je dois dire que j'aimais la notation hongroise. J'ai travaillé à Microsoft quelques années de retour et j'aime Joel j'ai appris les applications hongroises et non la variante cassée. Il est vraiment faire un mauvais code look .


1 commentaires

Tout ça sonne bien ... mais je suis content d'avoir des gens comme Eric Lippert, en mettant la structure en place sans me soulever un doigt.



2
votes

La réponse de Andrew est une bonne, mais j'ajouterais également une discipline à la liste. Je trouve qu'après suffisamment de pratique avec C ++ que vous obtenez une très bonne idée de ce qui est sûr et de ce qui est Mending pour les velocirapteurs à venir vous. Vous avez tendance à développer un style de codage qui se sent à l'aise lorsque vous suivez les pratiques sécurisées et vous laisse sentir les Heebie-Jeebies devriez-vous essayer de jeter un pointeur intelligent à un pointeur cru et transmettez-le à quelque chose sinon.

J'aime penser à cela comme un outil électrique dans un magasin. Il est suffisamment sûr une fois que vous avez appris à l'utiliser correctement et tant que vous vous assurez toujours de toujours suivre toutes les règles de sécurité. C'est quand vous pensez que vous pouvez renoncer aux lunettes de sécurité que vous êtes blessé.


0 commentaires

1
votes

J'ai fait à la fois C ++ et C # et je ne vois pas tout le battage médiatique sur le code géré.

Oh, à droite, il y a un collecteur des ordures pour la mémoire, c'est utile ... Sauf si vous ne vous abstenez pas d'utiliser des pointeurs anciens simples en C ++, si vous n'utilisez que Smart_pointers, vous n'avez pas beaucoup de problèmes.

Mais alors j'aimerais savoir ... Est-ce que votre collection-ordures vous protège de:

  • Garder les connexions de base de données ouvertes?
  • garder les serrures sur des fichiers?
  • ...

    Il y a beaucoup plus à la gestion des ressources que la gestion de la mémoire. La bonne chose est C ++ est que vous apprenez rapidement à quelle gestion les ressources et au RAII signifie afin qu'il devienne un réflexe:

    • Si je veux un pointeur, je veux un auto_ptr, un partage_ptr ou un faible_ptr
    • Si je veux une connexion DB, je veux un objet 'Connection'
    • Si j'ouvre un fichier, je veux un objet 'Fichier'
    • ...

      comme pour les dépassements de tampon, eh bien, ce n'est pas comme si nous utilisons du char * et Taille_t partout. Nous avons certaines choses appelez «chaîne», «iostream» et bien sûr le vecteur déjà mentionné :: sur la méthode qui nous libère de ces contraintes.

      Les bibliothèques testées (STL, Boost) sont bonnes, utilisez-les et passez à des problèmes plus fonctionnels.


3 commentaires

Les connexions de base de données et les serrures de fichiers sont un hareng rouge. Il existe des modèles simples et bien établis pour ceux-ci dans des langues gérées. En C # c'est l'instruction "Utilisation", qui dispose automatiquement des ressources lorsqu'elles ne sont plus nécessaires.


IMO Le principal problème avec les pointeurs intelligents en C ++ est qu'il n'y a pas de norme réelle. Si vous utilisez des bibliothèques / frameworks tiers, il est très peu probable qu'ils utilisent tous le même type de pointeur intelligent. Vous pouvez donc compter sur eux dans un module, mais dès que vous interfacez des composants de différents fournisseurs, vous revenez à la gestion de la mémoire manuelle.


@nikie: Lorsque j'utilise des composants tiers, je m'attends à ce qu'ils soient très clairs sur leur stratégie de gestion de la mémoire. Mais ensuite, les seules 3ème bibliothèques que nous avons au travail sont OpenSource comme Boost ou Cyptopp, donc je n'ai pas beaucoup d'expérience là-bas.



1
votes

à côté de beaucoup de bons conseils donnés ici, mon outil le plus important est sec - ne vous répétez pas vous-même. Je ne répandre pas le code sujet aux erreurs (par exemple pour la manipulation des allocations de mémoire avec MALLOC () et GRATUIT ()) partout sur mon codebase. J'ai exactement un seul emplacement dans mon code où Malloc et Free sont appelés. C'est dans les fonctions Wrapper Memylalloc et Mematefree.

Il y a toute la vérification des arguments et la gestion des erreurs initiale qui est généralement donnée en tant que code de chaudron répété autour de l'appel à MALLOC. En outre, il permet de modifier un seul emplacement, en commençant par de simples chèques de débogage, tels que compter les appels réussi à Malloc et gratuitement et vérifier lors de la résiliation du programme que les deux chiffres sont égaux, jusqu'à toutes sortes de vérifications de sécurité étendues. p>

Parfois, quand j'ai lu une question ici, j'ai toujours "je dois toujours m'assurer que Strncpy met fin à la chaîne, existe-t-il une alternative?" P>

char *my_strncpy (dst, src, n)
{
    assert((dst != NULL) && (src != NULL) && (n > 0));
    strncpy(dst, src, n);
    dst[n-1] = '\0';
    return dst;
}


0 commentaires

3
votes

J'ai utilisé C ++ pendant 10 ans. J'ai utilisé C, Perl, Lisp, Delphi, Visual Basic 6, C #, Java et diverses autres langues que je ne me souviens pas du sommet de ma tête.

La réponse à votre question est simple: Vous devez savoir ce que vous faites , plus que c # / java. Le plus que est ce qui abeille de tels rants tels que Jeff Atwood's concernant " Java Schools ".

La plupart de vos questions, dans un sens, sont absurdes. Les "problèmes" que vous montissez sont simplement des faits de la manière dont le matériel fonctionne vraiment . J'aimerais vous défier d'écrire une CPU & RAM dans VHDL / Verilog et voyez comment les choses fonctionnent vraiment, même lorsque vraiment simplifié. Vous commencerez à apprécier que la voie C # / Java est une abstraction papier sur le matériel.

Un défi plus facile serait de programmer un système d'exploitation élémentaire pour un système intégré à partir de la mise sous tension initiale; Ça vous montrera ce que vous devez savoir aussi.

(J'ai aussi écrit C # et Java)


3 commentaires

Poser des questions fait partie du processus d'arrivée à l'endroit où vous "savez ce que vous faites."


Je ne vous frappe pas, Robert. Je vous ai donné de ma meilleure compréhension de la façon dont vous programmez en toute sécurité en dehors du code VM, ainsi que d'un itinéraire pour comprendre les vraies machines.


J'apprécie que et le fait que C / C ++ soit utilisé beaucoup dans des systèmes embarqués; Clairement, il est plus proche du métal que d'autres langues comme Java.



3
votes

Nous écrivons en C pour les systèmes embarqués. En plus d'utiliser certaines des techniques communes à tout langage ou environnement de programmation, nous employons également:

  • Un outil d'analyse statique (par exemple PC-Lint ).
  • Conformité à Misra-C (appliqué par l'outil d'analyse statique).
  • Aucune allocation de mémoire dynamique du tout.

0 commentaires