6
votes

résultat inattendu lors de l'ajout au pointeur

Quelqu'un m'a dit ce bit de code Print 29. Pourquoi est-ce? XXX


19 commentaires

Pensez à un titre utile et à mettre le code dans le corps de la question.


Ce code ne compile même pas, alors comment peut-il imprimer quelque chose? Erreur: une valeur de type "INT" ne peut pas être utilisée pour initialiser une entité de type "int *"


@Unclebens - Malheureusement, c'est acceptable pour 'C'.


@Scottie, je ne pense pas que ce soit une amélioration sur ma version concernant la recherche.


@GF, je viens de le retourner à ma version.


Souhaitez-vous arrêter de modifier ma question? Motti Targia ;-)


@Scottie: Oh, j'ai négligé cette ligne. @Hellfrost: Voir "D'autres personnes peuvent modifier mes affaires ?!" dans la FAQ;)


Bien, mais laissez la ligne de tête seule. au moins pour l'amour des URL


Je ne veux pas entrer dans une discussion prolongée ici, mais avoir un titre descriptif qui peut être recherché fait partie du concept de SO.


Puis-je obtenir un lien Perma à la question?


@Hellfrost: Stackoverflow.com/Questtions/1910317 Il suffit de déposer la chaîne après l'ID.


@Aaron: incorrect. Il est explicitement pas acceptable dans C. Cela pourrait être acceptable dans certains compilateurs de buggy, mais pas dans C.


@Andrey, merci beaucoup pour vos commentaires et de clarifier la norme pour moi. C'est incroyablement surprenant que la mauvaise réponse ait reçu plus de 30 votes!


@Andreyt - Cela fonctionne bien dans GCC 3.4.4 et GCC 4.3.2 et Visual C 2008 - pas exactement "certains compilateurs de buggy".


Si l'un de ces compilateurs ne vous avertit pas, ils ne sont pas conformes aux normes. Vous avez besoin des bonnes options pour compiler en mode C standard. Pour GCC, essayez -ansi -peantique . En fait, je viens d'essayer, et GCC sans option vous donne un avertissement: Avertissement: l'initialisation rend le pointeur d'entier sans couler . Le fait que cela continue à compiler la source et à faire quelque chose qui ne signifie rien pour cette discussion. Le code a besoin d'une distribution et avec la distribution, le comportement est défini la mise en œuvre. Sans le casting, c'est faux.


@Alok - La norme C ne définit pas les avertissements. Il définit ce qui est autorisé pour la langue. Si la déclaration n'était pas acceptable dans C, alors ce serait une erreur, pas un avertissement. GCC a beaucoup d'avertissements pour des choses parfaitement acceptables C mais sont susceptibles d'être un code incorrect.


@Aaron, la norme C définit les diagnostics requis. Si un compilateur ne publiait pas un message de diagnostic pour le code ci-dessus, il n'est pas conforme. La norme ne doit pas besoin quoi que ce soit, à l'exception de #Error de directives pour résulter de "compiler". Mais il définit ce qui est un programme conforme, et ce qui précède n'est pas. Appelez-le ce que vous voulez, ce n'est pas Ansi / Iso C.


@Hellfrost En fait, tout lien vers cette question est un lien permanent, car le numéro de la question ne change pas, et les mots après ignorés par le serveur, ce lien fonctionne également: Stackoverflow.com/Questtions/1910317/forks-spoons-knives


@Aaron: Apparemment, vous n'êtes pas très familier avec GCC. Le premier fait que vous devriez en apprendre davantage sur GCC, c'est que le grand nombre d'avertissements de la GCC sont des "erreurs" en réalité, du point de vue de C (violations de contraintes). Pour cette raison, GCC seul est absolument inutile pour vérifier la validité du code C (tant que vous essayez de compter sur sa séparation des messages de diagnostic en «erreurs» et «avertissements»). Si GCC vous donne un "avertissement", vous devez toujours faire des recherches supplémentaires pour déterminer si c'est vraiment une "erreur". Pour ce cas, la recherche a déjà été faite: c'est une "erreur". Point final.


5 Réponses :


36
votes

Parce que lorsque vous ajoutez à un pointeur, il ajoute la taille de l'objet. Dans ce cas, la taille de l'objet est 4 (tailleOf (int) == 4) - SO 17 + 3 * 4 == 29.


25 commentaires

Bonjour Aaron, il y a une faute de frappe dans votre réponse. Il devrait être 17+ 3 * 4 = 29.


@Zinx ... HA - Cette erreur était là pour toutes les 5 secondes et que vous vous trouviez de la repérer ...


D'où vient-il? A est un pointeur - vous n'êtes pas la déséroférance, donc vous n'allez pas obtenir le remonte à 17 ans. Ce que cela va faire, prend l'adresse 12 du passé partout où "A" est et imprime la valeur "Entier" à cette adresse.


En fait, c'est correct. Vous attribuez à la valeur littérale "17" au pointeur - il fait donc pointer l'adresse mémoire 17. Après avoir ajouté 4 INTS, il pointe à l'adresse mémoire 29. Ainsi, lorsque vous demandez d'imprimer la valeur du pointeur (comme opposé à la valeur de ce qu'il pointe), il imprimera 29.


@Anon. Quel idiot - j'aurais dû comprendre que lorsqu'il attribue 17 ans, il déclare un, pas la déséroférance. Merci!


La norme C ne garantit pas que même la lecture d'un tel pointeur a du sens, et c'est un comportement indéfini. De plus, étant donné que tailleof (int) peut être (et est) différent sur différents environnements et qu'il imprime un pointeur en utilisant "% d", tout peut arriver. Qu'il a reçu 29 est une coïncidence. Enfin, sur certaines machines, une telle adresse pourrait être une représentation de piège, ce qui signifie que PrintF échouera spectaculairement.


Pourriez-vous expliquer comment la lecture du nombre "17" de la valeur de la pile pourrait causer une "défaillance spectaculaire"? L'adresse mémoire 17 n'a rien à voir avec cela.


C99 Draft, section 6.3.2.3 (emphase des miennes): un entier peut être converti à n'importe quel type de pointeur. Sauf comme indiqué précédemment, le résultat est défini par la mise en œuvre, peut ne pas être correctement aligné , pourrait ne pas pointer une entité du type référencé, et peut être une représentation de piège . ("précédemment défini" est pour l'entier 0). Ensuite, il est écrit: tout type de pointeur peut être converti en un type entier. Sauf comme spécifié précédemment, le résultat est défini par la mise en œuvre. Si le résultat ne peut pas être représenté sous le type entier, le comportement est indéfini.


La variable du pointeur n'est jamais différée, elle ne provoque donc pas de piège et l'alignement n'a pas d'importance. Si le programme avait une autre ligne, * A = 0 Cela causerait un crash sur la plupart du matériel - mais cela n'a pas cela.


@Chris: (A + 3) == 17 mais * (A + 3) == programme écrasé . Cette petite étoile fait une grande différence!


Le désalignement n'a pas d'importance, parce que nous ne la dérangeons pas. De même, il ne s'agit pas non plus d'une représentation du piège.


@Kevin: Non, ce n'est pas garanti. La représentation du piège est le pointeur lui-même. Il s'agit d'un comportement indéfini en C d'utiliser une valeur de piège, que vous soyez de la désarférence ou non. Par exemple, la CPU pourrait graner immédiatement uniquement si vous copiez un pointeur de représentation d'un piège dans un registre de pointeur.


Anon, Chris et Kevin: Veuillez lire la citation que j'ai postée ci-dessus de la norme. Oui, nous savons tous que "cela fonctionne", mais il n'y a aucune garantie de la norme qu'elle le fera, et il y a / pourrait être des architectures sur lesquelles des choses comme celle-ci "ne fonctionnent pas". En d'autres termes, le code n'est pas portable.


@Alok - Vous êtes 100% correct. Il est possible que cela étouffe complètement ou imprimer une valeur d'une valeur supérieure à 29, et est inutile. Toutefois, sur la plupart des ordinateurs domestiques du monde (Wihch sont malheureusement x86) en utilisant la plupart des compilateurs (au moins GCC et Visual Studio), cela ne s'éteint pas et imprimera 29. Avec GCC, vous avez au moins un avertissement ...


@Alok: Vous devez regarder plus de prudence ce que vous lisez. Le texte de la norme que vous avez cité s'applique à conversions explicites spécifiées par l'utilisateur . C'est à dire. Vous pouvez faire int * a = (int *) 17 , mais vous ne peut pas faire int * a = 17 . Le plus tard est explicitement, strictement et incontestable dans C (et en C ++). Tout compilateur qui ne parviendrait pas à signaler cette erreur flagrante serait ri au bord du marché tout de suite.


@Anon: incorrect. Il n'est pas possible de "attribuer une valeur littérale 17" à un pointeur sans une distribution explicite dans C. Le code n'est pas valide.


@Andrey: merci. Donc, sans cas, int * a = 17 est une violation de contraintes. Avec la distribution, c'est défini la mise en œuvre. L'appel Printf est horriblement faux de toute façon, car il imprime un pointeur avec% D sans prototype dans la portée (et même s'il y avait un prototype, il aurait toujours besoin de lancer l'argument). Je suis heureux d'apprendre la différence entre int * a = 17 et int * a = (int *) 17 .


@Andreyt - Vous êtes incorrect. Cette déclaration est parfaitement légale C. Je viens de l'essayer dans la GCC 3.4.4 et 4.3.2 - qui est un compilateur assez mature et n'est pas tout à fait de faire rire du marché tout de suite. '


@Aaron: Lorsque vous compilez avec gcc -w -wall -ansi -peantique , vous avertit-il? Par défaut, gcc compile une langue appelée "gnu c", non standard C. voir man gcc , -std = option pour plus de détails.


@Alok - Oui, cela donne un avertissement. AVERTISSEMENT! = 'Il n'est pas possible d'attribuer une valeur littérale 17 "à un pointeur sans une distribution explicite dans C. Le code n'est pas valide.' Oui - le code est la merde et absurde mais ce n'est pas une erreur.


@Aaron: incorrect. La déclaration est illégale dans C. GCC vous a donné un avertissement. "AVERTISSEMENT" est une forme valide de message de diagnostic dans C. Du point de vue linguistique, il n'y a aucune différence entre les avertissements et les erreurs. Le GCC est connu pour ses extensions de compilateur rampisant, qui le prennent bien au-delà de la portée du légal C. C'est pourquoi vous n'utilisez jamais CGC pour tester la validité du code et même si vous le faites, vous l'exécutez avec -ansi - Pedantic-Errors -Wall et traitez tous les avertissements comme des violations de contraintes potentielles (qui nécessitent des recherches supplémentaires).


@Aaron: Mais mieux encore, arrêtez de gaspiller le temps de votre peuple et de faire des expériences inutiles avec GCC à cette fin. Si vous souhaitez tester la validité du code en essayant de la compiler, la première place à partir est Comeau Online, pas de GCC.


@Aaron: Encore une fois, une "erreur" n'a pas de "erreur" dans C. Lorsque quelqu'un dit que le code est invalide et non compilable, cela signifie que le code contient une infinalité de contrainte aussi appelée . Lorsque le compilateur trouve une violation de contrainte, il est nécessaire de délivrer un message de diagnostic . Le compilateur n'est pas requis pour arrêter la suppilation, il est simplement nécessaire de vous informer que votre code n'est pas valide C. C'est exactement ce que fait la GCC. Cela vous a dit: ce n'est pas c, mais je vais le compilerai de toute façon en quelque sorte. Donc, encore une fois, essayez simplement de le mémoriser pour le moment (et comprendre plus tard): int * a = 17 n'est pas un code C valide.


@Aaron: Vous avez reçu des liens vers la norme linguistique, vous avez reçu des références à Comiler Comeau Online Compiler, qui vous fournira plus de diagnostic de Pedantic dans ce cas. Si vous ne l'obtenez toujours pas, alors désolé, il n'y a rien que je puisse faire.


@Andreyt - Vous pouvez réellement être correct. Cependant, votre attitude condescendante et votre divertissement me font penser que vous êtes juste compensant et que je devrais vous ignorer, ce que je ferai maintenant.



11
votes
a+3 == a + (3 * sizeof(int)) == a + 12 == 17 + 12 == 29

0 commentaires

-2
votes

peut imprimer quelque chose .. Vous définissez un pointeur sur l'emplacement '17' en mémoire ...


9 commentaires

Pas assez. S'il a essayé de Déréférence a il obtiendrait presque certainement un segfault, mais il imite la valeur du pointeur comme in int.


@Js: Pas tout à fait. Il n'y a aucune garantie que 17 est une adresse valide et pourrait être une représentation de piège. Lire simplement qu'un tel pointeur est une erreur (qu'il fait en faisant A + 3 ).


Comportement non défini, à coup sûr. Cela pourrait imprimer quelque chose et être conforme. Heck, cela pourrait reformater tous vos fichiers .doc comme haïku et être conformes.


@Davidthornley, ceci (et le meme de nez bleu-démons-là en général) me donne envie d'écrire un compilateur C qui agressivement fait des choses ridicules lorsqu'elles sont présentées avec un comportement indéfini.


Ou, comme on dit dans comp.lang.c , il peut faire "Daemons s'envoler de ton nez"!: groups.google.com/group/comp.std.c/msg/dfe1ef367547684b


Dans comp.lang.c Vous obtiendriez immédiatement expliqué que int * a = 17 n'est pas légal c et le débat serait terminé. Aucun démons impliqué. Le seul potentiel démoniaque Ce code a l'utilisation de % d pour imprimer des pointeurs. Mais cela n'entrerait en jeu qu'après que le int * A = 17 est corrigé.


@Andrey: Néanmoins, les démons nasaux sont possibles avec int * a = 17 . Avec int * a = (int *) 17 , c'est défini la mise en œuvre, et j'espère qu'il n'existe pas d'implémentation qui dit "si vous le faites, il y aura des daemons" :-)


Je crois que les démons nasaux sont démoniables par opposition au bienveillant (DaeMonic).


L'utilisation incorrecte de «démon» conduit à des démons nasales? J'espère que non. Je ne penser aux démons nasaux!



13
votes

Tout le monde sait que la réponse est 23, au moins sur le 6809.

a+3 == a + (3 * sizeof(int)) == a + 6 == 17 + 6 == 23


4 commentaires

+1 pour me souvenir de me souvenir des maux de tête terribles programmation Motorola 68K;)


+1 pour le facteur de nostalgie. Je connaissais le 6809 bien, il y a de nombreuses années. S'il vous plaît dites-moi que vous n'utilisez pas toujours? :)


@Steve: Non, je ne l'utilise pas aujourd'hui. J'ai écrit mon premier compilateur C et j'ai toujours un émulateur HMS au sous-sol.


De retour quand j'avais une machine de 6809, je l'ai aimé. Il était tellement plus facile de programmer l'assembleur que ma boîte Z80. (BTW, cela aurait travaillé sur mes compilateurs de Mac C antérieurs. Il a fallu un certain temps avant la LightSpeed ​​C, pense C et Codewarrior C a commencé à utiliser des INT 32 bits.)



2
votes

In Connectés de langue ne peut pas être initialisé avec des valeurs intégrales, à la seule exception d'une expression constante intégrale qui évalue à zéro intégré. 17 ne satisfasse pas à cette exigence.

Votre code n'est pas valide. Il ne "imprime" rien. La question n'a aucun sens que ce soit. Toute tentative d'analyser cette question du point de vue de l'arithmétique du pointeur est ridicule et juste une perte de temps inutile.


ISO / IEC 9899: 1999 (Programmes de programmation - c)

6.5.16.1 Affectation simple

contraintes

L'un des éléments suivants doit tenir: 93)

- L'opérande gauche a un type arithmétique qualifié ou non qualifié et le droit a type arithmétique;

- L'opérande gauche a une version qualifiée ou non qualifiée d'une structure ou d'un type d'union compatible avec le type de droite;

- Les deux opérandes sont des pointeurs vers des versions qualifiées ou non qualifiées de types compatibles, et le type pointé vers la gauche a tous les qualificatifs du type pointé par le droit;

- un opérande est un pointeur sur un objet ou un type incomplet et l'autre est un pointeur à un une version qualifiée ou non qualifiée de vide, et le type pointé à gauche a tout les qualificatifs du type pointé vers la droite;

- L'opérande gauche est un pointeur et la droite est une constante de pointeur nulle; ou

- L'opérande gauche a le type _bool et le droit est un pointeur.

93) L'aspect asymétrique de ces contraintes par rapport aux qualificatifs de type est due à la conversion (spécifié dans le 6.3.2.1) qui change de lvalues ​​à "la valeur de l'expression" "qui supprime tout type qualificatifs de la catégorie type de l'expression.


14 commentaires

MDR! Je pense qu'il serait difficile de sortir avec un exemple plus flagrant de l'échec du système de vote. Plusieurs réponses bogus ont été avancées aussi élevées que 30, tandis que le seul correct a été corrigé :)) est tout le monde avec une connaissance au moins de base de C déjà en vacances de Noël?


Sauf que chaque compilateur, je peux avoir mes mains sur imprime 29 ou 23.-) Dans la vie réelle, les gens attribuent des adresses aux pointeurs tout le temps. Si cela n'a pas fonctionné, les avions n'écraseraient pas, les scanners du CT ne seraient pas, OSES ne fonctionnerait pas, etc. Beaucoup de gens supposent que ce comportement "indéfini" fait ce qu'ils veulent. Et, je soupçonne, un vendeur de compilateur qui a vraiment rendu cela indéfini aurait plusieurs clients tristes et anciens. (... comme je démarre pour définir le bit de cache de dérivation sur le pointeur d'adresse de la mémoire tampon du pilote Ethernet)


Premièrement, si la langue Starndard dit que le code est illégal - ce n'est plus c, peu importe ce que vos compilateurs "imprimés". Deuxièmement, vous devez ignorer les messages de distinction émis par votre compilateur. Ce n'est pas une bonne idée. Lorsque vous ignorez les messages de diagnostic, cela n'a aucun sens de regarder le programme "Prints". Il est inutile de toute façon. Si votre compilateur ne génère aucun diagnostic, votre compilateur est une poubelle risible.


Troisièmement, le premier compilateur que vous devriez essayer de telles choses est toujours Comeau en ligne. Je suis surpris que je dois mentionner quelque chose que je pensais être une connaissance courante. Comeau Online signale correctement l'erreur, qui ferme le débat.


Quant aux «personnes attribuent des adresses aux pointeurs tout le temps» - oui, ils le font. Mais ils le font, comme la langue nécessite, avec une fonte explicite. Dans ce cas, ce serait int * a = (int *) 17 . Les gens qui le font "tout le temps" sans casting, comme dans l'Op, se retrouvent très rapidement des hamburgers. Ce dernier principe ( INT * A = 17 => Go Flip Burgers) est la raison réelle pour laquelle les avions ne se blagent pas et les scanners de CT numérisent.


J'aime pousser des boutons. ;-) En effet, tous les avertissements émis par le compilateur. Tout le monde n'utilise-t-il pas -werror? Ils sont toujours imprimés 29 ou 23.


+1 J'aime le commentaire des hamburgers. Parfois, je souhaite, après 30 ans de programmation intégrée, que j'avais un passage plus long de burgers à basculer. ;-)


Au fait, j'ai écrit le compilateur 6809 C J'ai mentionné dans un autre commentaire lorsque le 6809 était neuf . S'il vous plaît ne faites pas le calcul.


Génial, mais la seule chose qu'il montre, c'est que, dans certains cas, les personnes qui écrivent des compilateurs de C n'ont pas une connaissance appropriée de la langue qu'ils sont censées compiler. Est-ce que c'est bon? C'était OK en 1985, mais en 2009, il est inexcusable. Et plus tard, d'autres personnes utilisent ces compilateurs pour tester l'exactitude de leur code C :))), en perpétrant ainsi le cycle de l'ignorance C.


Depuis que vous semblez manquer d'un sentiment d'humour et d'une capacité à lire et à comprendre le mot écrit, je me désengagerai.


La question n'est pas ridicule. Lorsque le casting (INT *) est ajouté au littéral 17, il devient légal c code, oui? Qui imprime le numéro 29, non? (Lorsque Tailleof (int) est 4, ce qu'il est souvent.) Vous pouvez être correct que le code est incorrect, mais de nombreux compilateurs l'acceptent de toute façon (et émettent des messages de diagnostic). Vous avez fait votre point, mais quand vous allez dire que cette discussion est une perte de temps, c'est irrespectueuse.


@Kevin: non ce n'est pas le cas. Afin de devenir légal avec % d Spécificateur de format utilisé dans printf Il a également besoin de la valeur de l'argument du pointeur sur printf pour être explicitement renvoyé à int . Mais même cela ne fait pas une question sur la valeur, il pourrait imprimer une question valable, car tout le temps est fortement dépendant de la plate-forme.


Ce que beaucoup de personnes ici ne réalisent ici que sur un entretien d'embauche (par exemple), ce serait une question d'astuce classique. Tout candidat qui irait dans le pointeur arithmétique étant donné le code ci-dessus, serait entendu avec un sourire poli et donnerait une marque «échoue» sur la question. "Des énigmes" comme celle-ci sont spécifiquement conçues pour vérifier si vous êtes assez bon pour noter l'initialisation du pointeur non valide et le spécificateur de format invalide. Des points supplémentaires sont donnés pour mentionner que printf (fonction variadique) nécessite une déclaration avant avant utilisation.


Je me demande combien d'intervieweurs voudraient également entendre que c'est défini sur la mise en œuvre, que ce soit (int *) 12 est une représentation de piège et que, si c'est le cas, le comportement du reste du code est indéfini. . Probablement pas beaucoup, mais je pense que cela vaut la peine de garder à l'esprit que tous les intervieweurs n'exigeront pas nécessairement la même réponse à cette question. Si vous êtes interviewé pour une programmation d'emploi Windows en C, et ce code compile sur MSVC et Intel Compilers, ils ne se soucient pas de savoir si vous savez que c'est illégal, ils pourraient simplement vouloir la réponse Windows.