[Powered by Google Translate] [Section 5 - Plus confortable] [Rob Bowden - Université de Harvard] [C'est CS50. - CS50.TV] Comme je l'ai dit dans mon e-mail, il ya beaucoup de choses que vous pouvez utiliser autre que l'appareil de réellement faire les séries de problèmes. Nous vous recommandons de le faire dans l'appareil juste parce que nous pouvons plus facilement vous aider et nous savons comment tout va fonctionner. Mais comme un exemple de situation où vous pouvez faire des choses si, par exemple, vous n'avez pas accès à un appareil ou si vous voulez travailler dans le sous-sol Science Center - ce qui fait qu'ils ont l'appareil trop - si vous voulez travailler n'importe où. Un exemple est que vous avez vu / entendu parler de SSH? SSH est fondamentalement juste comme se connecter à quelque chose. En fait, en ce moment je suis SSHed dans l'appareil. Je ne travaille jamais directement dans l'appareil. Voici l'appareil, et si vous regardez en bas ici, vous voyez cette adresse IP. Je ne travaille jamais dans l'appareil lui-même; J'ai toujours venir à une iTerm2 fenêtre / fenêtre de terminal. Vous pouvez SSH à cette adresse IP, ssh jharvard@192.168.129.128. Je me souviens que nombre très facilement parce que c'est un joli motif. Mais qui va me demander mon mot de passe, et maintenant je suis dans l'appareil. Fondamentalement, à ce stade, si vous avez ouvert un terminal intérieur de l'appareil lui-même, cette interface, mais vous devez l'utiliser, est exactement le même que l'interface que j'utilise ici, mais maintenant vous êtes SSHed. Vous n'avez pas besoin de SSH à l'appareil. Un exemple d'un autre endroit que vous pourriez SSH, c'est que je suis sûr que vous avez par défaut - Oh. Bigger. Chacun d'entre vous doit avoir de comptes SAF par défaut sur les serveurs du SAF. Pour moi, je le ferais SSH pour rbowden@nice.fas.harvard.edu. Il va vous demander que la première fois, et vous dites oui. Mon mot de passe est juste va être mon mot de passe SAF. Et maintenant, je suis SSHed les serveurs sympas, et je peux faire ce que je veux ici. Un grand nombre de classes que vous pourriez prendre, comme 124, vont avoir des choses à vous téléchargez ici à soumettre effectivement vos ensembles de problèmes. Mais dire que vous n'avez pas accès à votre appareil. Ensuite, vous pouvez faire des choses, comme sur ici il va dire: - C'est juste notre section de questions. Il vous sera demandé de le faire dans l'appareil. Au lieu de cela, je vais le faire sur le serveur. Je vais décompresser ce. Le problème va être que vous êtes habitué à utiliser quelque chose comme gedit ou autre à l'intérieur de l'appareil. Vous n'allez pas avoir ce sur le serveur SAF. Il est tout simplement va être cette interface textuelle. Ainsi, vous pouvez une ou l'autre, essayer d'apprendre un éditeur de texte qu'ils ont. Ils ont Nano. Nano est généralement assez facile à utiliser. Vous pouvez utiliser vos flèches et taper normalement. Donc, ce n'est pas difficile. Si vous voulez vraiment fantaisie, vous pouvez utiliser Emacs, que je n'aurais probablement pas ouvert parce que je ne sais même pas comment fermer Emacs. X Control, Contrôle C? Ouais. Ou vous pouvez utiliser Vim, qui est ce que j'utilise. Et si ce sont vos options. Si vous ne voulez pas le faire, vous le pouvez aussi, si vous regardez manual.cs50.net-- Oh. Sur un PC, vous pouvez utiliser SSH PuTTY, que vous allez avoir à télécharger séparément. Sur un Mac, vous pouvez tout simplement par l'utilisation du terminal par défaut ou vous pouvez télécharger iTerm2, qui est comme une belle Terminal fantaisie. Si vous allez à manual.cs50.net vous verrez un lien vers Notepad + +, qui est ce que vous pouvez utiliser sur un PC. Il vous permet de SFTP à partir de Notepad + +, qui est essentiellement SSH. Que cela vous permettra de faire est d'éditer vos fichiers en local, et puis quand vous voulez les sauver, il vous fera économiser de nice.fas, où vous pouvez ensuite les faire fonctionner. Et l'équivalent sur Mac va être TextWrangler. Donc, il vous permet de faire la même chose. Il vous permet d'éditer des fichiers en local et de les enregistrer sur nice.fas, où vous pouvez ensuite les faire fonctionner. Donc, si jamais vous êtes coincé sans un appareil, vous disposez des options à toujours faire vos ensembles de problèmes. Le seul problème va être que vous n'allez pas avoir la bibliothèque CS50 parce nice.fas ne pas par défaut l'avoir. Vous pouvez soit télécharger la bibliothèque CS50 - Je ne pense pas que j'ai besoin en ce moment. Vous pouvez soit télécharger la bibliothèque CS50 et de le copier vers nice.fas, ou je pense qu'à ce stade, nous ne l'utilisons pas plus de toute façon. Ou si nous le faisons, vous pouvez pour le moment de le remplacer par les implémentations des fonctions de la bibliothèque CS50 toute façon. Donc, cela ne devrait pas être que beaucoup de restrictions. Et c'est tout. Je vais revenir à l'appareil maintenant, nous allons tout faire dans l'appareil. En regardant notre section de questions, au début, comme je l'ai dit dans mon email, nous devons parler de la courte que vous étiez censé regarder. Nous avons la Redirection & Tubes et ces trois questions. Pour ce courant ne fonctionne comme printf écrire par défaut? Alors flux. Qu'est-ce qu'un flux? Un flux est fondamentalement comme c'est juste une - Ce n'est même pas une source de 1 et de 0. Le flux c'est demander ici, c'est la sortie standard. Et de manière standard est un cours d'eau que lorsque vous écrivez à elle, il apparaît à l'écran. La sortie standard, par stream, et cela signifie que vous venez d'écrire 1 et de 0 à elle, et l'autre extrémité du lit sur tout type de ce flux. C'est juste une chaîne de 1 et de 0. Vous pouvez écrire dans les flux ou vous pouvez lire à partir de flux selon ce que le flux est en réalité. Les deux autres volets par défaut sont standard dans et erreur standard. Standard en est lorsque vous ne GetString, il vous attend pour des trucs d'entrée. Donc, il vous attend, il est effectivement en attente sur la norme en, ce qui est vraiment ce que vous obtenez lorsque vous tapez au clavier. Vous tapez dans pouces standard Erreur type est pratiquement équivalent à la sortie standard, mais il est spécialisé en ce que lorsque vous imprimez à l'erreur standard, vous êtes censé imprimer uniquement les messages d'erreur à ce afin que vous puissiez différencier les messages réguliers affichées à l'écran par rapport à des messages d'erreur suivant qu'ils allé à la sortie standard ou d'erreur standard. Trop de fichiers. La sortie standard, standard et l'erreur standard sont juste flux spéciaux, mais vraiment n'importe quel fichier, lorsque vous ouvrez un fichier, il devient un flux d'octets où vous pouvez juste lire à partir de ce flux. Vous, pour la plupart, peut il suffit de penser un fichier comme un flux d'octets. Donc, ce flux ne leur écrire par défaut? La sortie standard. Quelle est la différence entre> et >>? Quelqu'un at-il regarder la vidéo avant? D'accord. > Va être la façon dont vous rediriger vers des fichiers, et >> va également rediriger la sortie dans des fichiers, mais il est plutôt va ajouter dans le fichier. Par exemple, disons que j'ai la chance d'avoir dict ici, et la seule chose que l'intérieur du dict est chat, chat, chien, poisson, chien. Une commande que vous avez à la ligne de commande est le chat, qui va juste pour imprimer ce qu'il ya dans un fichier. Donc, quand je dis dict chat, il va imprimer chat, chat, chien, poisson, chien. C'est tout ce chat fait. Cela signifie qu'il imprimée sur la sortie standard chat, chat, chien, poisson, chien. Si je veux rediriger la place que pour un fichier, je peux utiliser> et le rediriger vers quel que soit le fichier est. Je vais appeler le fichier fichier. Alors maintenant, si I ls, je vois que j'ai un nouveau fichier appelé fichier. Et si je l'ouvre, il va avoir exactement ce chat mis à la ligne de commande. Alors maintenant, si je fais ça encore une fois, il va rediriger la sortie dans un fichier, et je vais avoir exactement la même chose. Donc, techniquement, il a complètement passé outre ce que nous avions. Et nous verrons si je change dict, j'ai sorti chien. Maintenant, si nous dict chat dans un fichier de plus, nous allons avoir la nouvelle version avec un chien enlevé. Donc, il remplace complètement. Au lieu de cela, si nous utilisons >>, il va ajouter le fichier. Maintenant, l'ouverture du fichier, nous voyons que nous avons exactement la même chose imprimée deux fois parce que c'était là une fois, nous annexés à l'original. C'est donc ce que> et >> faire. Est-ce que la prochaine demande - Il ne demande pas à ce sujet. L'autre est que nous avons <, qui, si> redirige la sortie standard, vous faites 2>, qui est rediriger l'erreur standard. Donc, si quelque chose se passait à l'erreur standard, il ne serait pas se mettre en txt2. Mais remarquez si je fais 2>, puis il est toujours l'impression Bonjour, Rob! à la ligne de commande parce que je ne fais que rediriger l'erreur-type, je ne suis pas rediriger la sortie standard. Erreur type et la sortie standard sont différentes. Si vous vouliez réellement écrire sur la sortie standard, alors je pourrais changer cela soit fprintf sur stderr. Donc, printf, par défaut, imprime sur la sortie standard. Si je veux imprimer sur la sortie standard manuellement, puis-je utiliser fprintf et de préciser ce que je veux imprimer. Si, au contraire je l'ai fait stdout fprintf, alors que c'est à peu près équivalent à printf. Mais fprintf d'erreur standard. Alors maintenant, si je rediriger ce dans txt2, Bonjour, Rob! est encore en train imprimée à la ligne de commande car il est imprimé à se faire d'erreur standard et je ne fais que rediriger la sortie standard. Si maintenant je rediriger l'erreur-type, maintenant cela n'a pas été imprimée, et txt2 va être Bonjour, Rob! Alors maintenant, vous pouvez imprimer vos erreurs réelles d'erreur standard et imprimer vos messages réguliers à la sortie standard. Et donc quand vous exécutez votre programme, vous pouvez l'exécuter en tant que. / Bonjour ce type avec le 2> de sorte que votre programme va fonctionner normalement, mais les messages d'erreur que vous pouvez vérifier que vous obtenez plus tard dans le journal des erreurs, tant d'erreurs, et ensuite chercher plus tard et que votre fichier aura des erreurs éventuelles erreurs qui se sont produites. Des questions? Le dernier est un tuyau, que vous pouvez considérer comme prenant la sortie standard d'une commande et ce qui en fait la norme de la prochaine commande. Un exemple est ici l'écho est une chose ligne de commande qui va tout simplement faire écho à ce que j'ai mis comme argument. Je ne vais pas mettre des guillemets. Echo blah, blah, blah va juste pour imprimer blah, blah, blah. Avant, quand j'ai dit que je devais mettre Rob dans un fichier txt car je ne peux rediriger les fichiers txt, au contraire, / si je fais écho à Rob puis redirigez vers. / bonjour, cela va aussi faire le même genre de chose. Cela prend la sortie de cette commande, Rob écho, et de l'utiliser comme entrée pour. / bonjour. Vous pouvez penser que c'est en tant que premier rediriger écho Rob dans un fichier et ensuite entrée dans. / bonjour ce fichier qui vient d'être émis. Mais il faut le fichier temporaire hors de l'image. Questions à ce sujet? La question suivante va impliquer ce sujet. Qu'est-ce pipeline pourrait que vous utilisez pour trouver le nombre de noms uniques dans un fichier appelé names.txt? Les commandes que nous allons avoir à utiliser ici sont uniques, uniq, puis wc. Vous pouvez le faire uniq homme fait regarder ce que fait, et il va tout simplement de filtrer les lignes adjacentes correspondant de l'entrée. Et l'homme wc va imprimer le saut de ligne, un mot, et le nombre d'octets pour chaque fichier. Et le dernier que nous allons avoir à utiliser est en quelque sorte, qui va simplement trier les lignes du fichier txt. Si je fais un fichier txt, names.txt, et c'est Rob, Tommy, Joseph, Tommy, Joseph, RJ, Rob, ce que je veux faire ici est de trouver le nombre de noms uniques dans ce fichier. Donc, quelle devrait être la réponse? >> [L'élève] 4. Ouais >>. Il doit être de 4 depuis Rob, Tommy, Joseph, RJ sont les seuls noms uniques dans ce fichier. La première étape, si je fais juste le nombre de mots sur names.txt, cela est fait de me dire tout. Il s'agit en fait d'impression - Voyons, l'homme wc - nouvelles lignes, de mots et de comptage d'octets. Si je ne se soucient que des lignes, alors je peux juste faire wc-l names.txt. Donc, c'est l'étape 1. Mais je ne veux pas wc-l names.txt parce names.txt ne contient que tous les noms, et je tiens à les filtrer tous les non-uniques. Donc, si je fais names.txt uniq, qui n'est pas tout à me donner ce que je veux parce que les noms dupliqués sont toujours là. Pourquoi est-ce? Pourquoi ne pas faire uniq ce que je veux? [L'élève] Les doublons ne sont pas [inaudible] >> Oui. Rappelez-vous la page de manuel pour uniq dit filtre lignes sans concordances adjacentes. Ils ne sont pas adjacents, afin de ne pas les filtrer. Si je les trier d'abord, names.txt genre va mettre toutes les lignes en double ensemble. Alors maintenant, names.txt sorte que. Je vais vouloir l'utiliser comme entrée uniq, qui est | uniq. Cela me donne Joseph, RJ, Rob, Tommy, et je veux l'utiliser comme entrée pour wc-l, qui va me donner 4. Comme il est dit ici, ce pipeline pourriez-vous utiliser? Vous pouvez faire beaucoup de choses comme l'utilisation d'une série de commandes où vous utilisez la sortie d'une commande comme entrée de la commande suivante. Vous pouvez faire beaucoup de choses, beaucoup de choses intelligentes. Des questions? D'accord. C'est tout pour les tubes et redirection. Maintenant, nous passons à la matière même, les trucs de codage. A l'intérieur de ce fichier PDF, vous verrez cette commande, et vous voulez exécuter cette commande dans votre appareil. wget est la commande pour simplement obtenir quelque chose de l'Internet, essentiellement, si wget et cette URL. Si vous êtes allé à cette URL dans votre navigateur, il serait de télécharger ce fichier. J'ai simplement cliqué sur elle, alors qu'elle le fichier téléchargé pour moi. Mais écrire wget de cette chose à l'intérieur de la borne va tout simplement de le télécharger sur votre terminal. J'ai section5.zip, et vous aurez envie de décompresser section5.zip, qui va vous donner un dossier appelé section5, qui va avoir tous les fichiers que nous allons utiliser aujourd'hui à l'intérieur de celui-ci. Comme les noms de fichiers de ces programmes suggèrent, ils sont un peu buggé, de sorte que votre mission est de comprendre pourquoi l'utilisation de gdb. Est-ce que tout le monde les ont téléchargés et / ou savoir comment obtenir les télécharger dans leur appareil? D'accord. Exécution ./buggy1, il va dire: Segmentation fault (core dumped), qui chaque fois que vous obtenez une erreur de segmentation, c'est une mauvaise chose. Dans quelles circonstances avez-vous obtenir une erreur de segmentation? [L'élève] déréférencement d'un pointeur nul. Ouais >>. Voilà donc un exemple. Déréférencer un pointeur NULL, vous allez obtenir une erreur de segmentation. Qu'est-ce une erreur de segmentation signifie que vous touchez mémoire que vous ne doit pas toucher. Donc déréférencement d'un pointeur null est en contact avec l'adresse 0, et, fondamentalement, tous les ordinateurs de nos jours dire que l'adresse 0 est la mémoire vous ne devriez pas être en contact. C'est pour cela que le déréférencement d'un pointeur NULL dans les résultats une erreur de segmentation. S'il vous arrive de ne pas initialiser un pointeur, alors qu'elle a une valeur ordures, et donc quand vous essayez de déréférencer, selon toute vraisemblance, vous touchez la mémoire c'est au milieu de nulle part. S'il vous arrive d'avoir de la chance et de la valeur des ordures qui est arrivé à pointer vers quelque part sur la pile ou quelque chose, puis quand vous déréférencer ce pointeur que vous n'avez pas initialisé, rien ne va mal tourner. Mais si elle pointe vers, disons, quelque part entre la pile et le tas, ou il est juste de pointer quelque part qui n'a pas été utilisée par votre programme encore, alors que vous touchez la mémoire vous ne devriez pas être en contact avec vous et erreur de segmentation. Lorsque vous écrivez une fonction récursive et récursivement trop de fois et votre pile devient trop importante et la collision de pile dans les choses qu'il ne devrait pas être entré en collision avec, vous touchez la mémoire vous ne devriez pas être en contact, de sorte que vous segfault. C'est ce qui est une erreur de segmentation. C'est aussi la même raison que si vous avez une chaîne comme - Revenons au programme précédent. En hello.c--Je suis juste faire quelque chose d'autre. char * s = "Bonjour tout le monde!"; Si j'utilise * s = quelque chose ou s [0] = 'X'; alors assurez-bonjour,. / bonjour, pourquoi est-ce une erreur de segmentation? Pourquoi est-ce une erreur de segmentation? Que feriez-vous m'attendre? Si je le faisais printf ("% s \ n", s); que voulez-vous faire imprimer? [L'élève] X bonjour. Ouais >>. Le problème est que lorsque vous déclarez une chaîne comme ceci, s est un pointeur qui va aller sur la pile, et ce que s est orientée vers cette chaîne est contenu dans la mémoire morte. Il suffit donc de par le nom, la mémoire morte, vous devriez avoir l'idée que si vous essayez de changer ce qu'il ya dans la mémoire morte, vous faites quelque chose que vous ne devriez pas faire avec de la mémoire et vous segfault. C'est en fait une grande différence entre char * s et char s []. Donc char s [], maintenant cette chaîne va être mis sur la pile, et la pile n'est pas en lecture seule, ce qui signifie que cela devrait fonctionner parfaitement bien. Et il le fait. Rappelez-vous que quand je fais char * s = "Bonjour tout le monde!", S elle-même est sur la pile mais s pointe vers un autre endroit, et que quelque part se trouve être en lecture seule. Mais char s [] est juste quelque chose sur la pile. C'est donc un autre exemple d'une erreur de segmentation se produit. Nous avons vu que ./buggy1 donné lieu à une erreur de segmentation. En théorie, vous ne devriez pas regarder buggy1.c immédiatement. Au lieu de cela, nous allons regarder à travers gdb. Notez que lorsque vous obtenez Segmentation fault (core dumped), vous obtenez ce dossier au cours de base appelé ici. Si nous ls-l, nous allons voir que le noyau est généralement un fichier assez gros. C'est le nombre d'octets du fichier, donc il semble que c'est quelque chose de 250 kilo-octets. La raison en est que ce que le core dump est en fait c'est quand votre programme plante, l'état de la mémoire de votre programme obtient juste copié et collé dans ce fichier. Il est rejeté dans ce fichier. Ce programme, alors qu'il était en cours d'exécution, qui est arrivé à une utilisation de la mémoire d'environ 250 kilo-octets, et c'est ce que me suis jeté dans ce fichier. Maintenant vous pouvez regarder ce fichier si nous faisons gdb buggy1 de base. Nous pouvons juste faire gdb buggy1, et ce sera tout simplement de démarrer gdb régulièrement, en utilisant buggy1 que le fichier d'entrée. Mais si vous faites gdb buggy1 de base, il est spécifiquement va démarrer gdb en consultant à ce fichier core. Et dites-vous buggy1 gdb moyen sait que le fichier core provient du programme buggy1. Donc gdb buggy1 coeur va nous apporter immédiatement à l'endroit où le programme est arrivé à terminer. Nous voyons ici Programme terminé avec le signal 11, Segmentation fault. Il nous arrive de voir une ligne d'assemblage, ce qui n'est probablement pas très utile. Mais si vous tapez bt ou trace, ça va être la fonction qui nous donne la liste de nos cadres de pile en cours. Donc trace. On dirait que nous n'avons que deux cadres de pile. Le premier est notre cadre de pile principale, et le second est le cadre de pile pour cette fonction que nous arrive d'être dans, qui ressemble nous n'avons que le code assembleur pour. Donc, revenons à notre fonction principale, et pour ce faire nous pouvons faire l'image 1, et je pense que nous pouvons aussi le faire vers le bas, mais je n'ai presque jamais le faire vers le bas - ou le haut. Ouais. De haut en bas. Vous amène jusqu'à une trame de pile, vers le bas vous ramène un cadre de pile. J'ai tendance à ne jamais l'utiliser. Je viens de dire précisément cadre 1, qui est d'aller vers l'image 1. Image 1 va nous mettre en stack frame principale, et il est dit ici de la ligne de code que nous arrive d'être à. Si nous voulions quelques lignes de plus de code, on peut dire la liste, et qui va nous donner toutes les lignes de code qui l'entourent. La ligne que nous crashait à été de 6: if (strcmp ("CS50 pierre", argv [1]) == 0). Si ce n'est pas évident, mais, vous pouvez l'obtenir directement d'ici simplement en pensant pourquoi il crashait. Mais nous pouvons aller encore plus loin et dire: «Pourquoi argv [1] erreur de segmentation?" Impression Faisons argv [1], et on dirait qu'il a 0x0, qui est le pointeur NULL. Nous strcmping CS50 roches et nulles, et donc ce qui se passe une erreur de segmentation. Et pourquoi est-argv [1] null? [L'élève] Parce que nous ne lui donne pas tous les arguments de ligne de commande. Ouais. Nous ne lui donne pas tous les arguments de ligne de commande. Donc ./buggy1 va seulement avoir argv [0] est ./buggy1. Il ne va pas avoir un argv [1], de sorte que se passe une erreur de segmentation. Mais si, au contraire, je fais juste CS50, il va dire Vous obtenez un D parce que c'est ce qu'il est censé faire. En regardant buggy1.c, il est censé print "Vous obtenez un D» - Si argv [1] n'est pas "CS50 rochers", "Vous obtenez un D", sinon "Vous obtenez un A!" Donc, si nous voulons un A, nous avons besoin de comparer ce aussi vrai, ce qui signifie que le compare à 0. Donc, argv [1] doit être "CS50" rochers. Si vous voulez faire cela en ligne de commande, vous devez utiliser \ pour échapper à l'espace. Donc CS50 \ roches et vous obtenez un A! Si vous ne faites pas la barre oblique inverse, pourquoi ça ne marche pas? [L'élève] Il s'agit de deux arguments différents. Ouais >>. Argv [1] va être CS50, et argv [2] va être rochers. D'accord. Maintenant ./buggy2 va segfault nouveau. Au lieu de l'ouvrir avec le fichier de base, nous allons simplement ouvrir buggy2 directement, si gdb buggy2. Maintenant, si nous venons de lancer notre programme, alors il va dire Program received signal SIGSEGV, qui est l'erreur de segmentation du signal, et c'est là que c'est arrivé à arriver. En regardant notre trace, nous voyons que nous étions dans la oh_no fonction, qui a été appelé par le dinky fonction, ce qui a été appelé par la fonction binky, qui a été appelé par le principal. Nous pouvons aussi voir les arguments de ces fonctions. L'argument de dinky et binky était de 1. Si nous listons les oh_no fonction, nous voyons que oh_no fait juste char ** s = NULL; * S = "BOOM"; Pourquoi en serait-il échoué? [L'élève] Vous ne pouvez pas déréférencer le pointeur NULL? Ouais >>. Ceci est juste que s est NULL, peu importe si ce qui arrive à être de type char **, qui, selon la façon dont vous l'interprétez, il pourrait être un pointeur vers un pointeur vers une chaîne ou un tableau de chaînes. C'est s est NULL, alors s * est déréférencer un pointeur NULL, et si cela va s'écraser. C'est l'une des façons les plus rapides, vous pouvez peut-être une erreur de segmentation. Il est juste de déclarer un pointeur nul et immédiatement des erreurs de segmentation. C'est ce que oh_no fait. Si nous remontons un cadre, puis nous allons entrer dans la fonction qui a appelé oh_no. J'ai besoin de le faire vers le bas. Si vous n'entrez pas de commande et que vous appuyez simplement de nouveau sur Entrée, il suffit de répéter la commande précédente que vous avez exécuté. Nous sommes dans le cadre 1. Énumérant ce cadre, nous voyons ici, c'est notre fonction. Vous pouvez frapper à nouveau la liste, ou vous pouvez faire la liste 20 et il donnera la liste plus. La fonction dinky dit que si i est 1, puis passez à la fonction oh_no, sinon, passez à la fonction slinky. Et nous savons que i est 1 car il nous arrive de voir ici que dinky a été appelé avec l'argument 1. Ou vous pouvez simplement imprimer puis-je et il va dire i est 1. Nous sommes actuellement en dinky, et si nous montons un autre cadre, nous savons que nous allons retrouver dans binky. Vers le haut. Maintenant, nous sommes dans binky. Inscription de cette fonction - la liste des avant la mi me couper - il a commencé comme si i vaut 0, alors nous allons l'appeler oh_no, sinon appelez dinky. Nous savons que i était de 1, donc il a appelé dinky. Et maintenant nous sommes de retour en principal et principal va juste être int i = rand ()% 3; Cela va juste pour vous donner un nombre aléatoire qui est soit 0, 1, ou 2. Il va appeler binky avec ce numéro, et il retournera 0. En regardant cela, tout marche à travers le programme manuellement sans l'exécuter immédiatement, vous devez définir un point d'arrêt au principal, ce qui signifie que lorsque nous lançons le programme votre programme s'exécute jusqu'à ce qu'il frappe une balle de break. Il faut donc lancer le programme, elle se déroulera, puis il va frapper la fonction principale et arrêter de courir. Maintenant, nous sommes à l'intérieur de la principale, et l'étape suivante ou va nous amener à la ligne de code suivante. Vous pouvez faire étape ou suivante. Frapper prochaine, maintenant je a été mis au rand ()% 3, afin que nous puissions afficher la valeur de i, et il va dire i est 1. Maintenant, c'est important que nous utilisions suivant ou pas. Je suppose que c'était important dans le précédent, mais nous voulons utiliser ensuite. Si nous utilisons étape, nous entrons dans la fonction, ce qui signifie oeil à la chose réelle ce qui se passe à l'intérieur de binky. Si nous utilisons prochaine, alors cela signifie aller sur la fonction et passez à la ligne suivante de code dans notre fonction principale. Ici, sur cette ligne, j'étais à où il est dit rand ()% 3; si je le faisais pas, il irait dans l'implémentation de rand et regardez ce qui se passe là-bas, et je ne pouvais parcourir la fonction rand. Mais je ne se soucient pas de la fonction rand. Je veux juste aller à la ligne suivante de code dans main, donc j'utilise prochaine. Mais maintenant, je me préoccupe de la fonction binky, donc je veux faire un pas dans cela. Maintenant, je suis dans binky. La première ligne de code va dire if (i == 0), je fais un pas, nous voyons nous nous retrouvons à dinky. Si nous listons les choses, nous voyons qu'il est vérifié i = 0. i n'est pas égal à 0, donc il est allé à la condition else, qui va appeler dinky (i). Vous pourriez devenir confus. Si vous regardez ces lignes directement, vous pourriez penser if (i == 0), OK, alors j'ai fait un pas et maintenant je suis à dinky (i), vous pourriez penser que cela doit vouloir dire i = 0 ou quelque chose. Non, cela signifie juste qu'il sait qu'il peut coller directement sur la ligne de dinky (i). Parce que je n'est pas 0, l'étape suivante ne va pas mettre fin à l'autre. Le reste est pas une ligne que ça va s'arrêter. Il va juste pour aller à la ligne suivante il peut effectivement exécuter, ce qui est très bien entretenue (i). En entrant dans dinky (i), nous voyons if (i == 1). Nous savons i = 1, alors quand nous entrons, nous savons que nous allons retrouver dans oh_no parce que i = 1 appelle la fonction oh_no, que vous pouvez entrer dans, qui va mettre en char ** s = NULL et immédiatement "BOOM". Et puis en fait en regardant la mise en œuvre de buggy2, cela, je ne fait que commencer un nombre aléatoire - 0, 1, ou 2 - appel binky, qui, si i est 0, il appelle oh_no, sinon il appelle dinky, qui vient ici. Si i est 1, oh_no appel, sinon appelez slinky, qui viennent ici, si i est 2, contactez oh_no. Je ne pense même pas il ya un moyen - Quelqu'un voit-il un moyen de rendre ce un programme qui ne sera pas une erreur de segmentation? Parce que si je ne me manque quelque chose, si i est 0, vous obtiendrez immédiatement une erreur de segmentation, sinon vous allez à une fonction qui, si i est 1, vous segfault, sinon vous allez à une fonction où si i est 2, vous segfault. Donc, peu importe ce que vous faites, vous segfault. Je suppose qu'une façon de fixer ce serait au lieu de faire char ** s = NULL, vous pourriez malloc espace pour cette chaîne. Nous pourrions faire malloc (sizeof) - sizeof quoi? [L'élève] (char) * 5? >> Cela vous semble juste? Je suppose que cela va fonctionner si j'ai effectivement couru, mais ce n'est pas ce que je recherche. Regardez le type de l'art. Ajoutons int *, int * x sorte. Je ferais malloc (sizeof (int)). Ou si je voulais un rayon de 5, je ferais (sizeof (int) * 5); Que faire si j'ai un int **? Que ferais-je malloc? [L'élève] Taille de l'aiguille. Ouais >>. (Sizeof (int *)); C'est la même chose ici-bas. Je veux (sizeof (char *)); Cela va allouer de l'espace pour le pointeur qui pointe vers "BOOM". Je n'ai pas besoin d'allouer de l'espace pour "BOOM" lui-même parce que c'est à peu près équivalent à ce que j'ai dit avant de char * x = "BOOM". "BOOM" existe déjà. Il arrive à exister dans la région en lecture seule de la mémoire. Mais il existe déjà, ce qui signifie que cette ligne de code, si s est de type char **, alors s * est un char * et vous l'avez installé char * pour pointer vers "BOOM". Si je voulais copier "BOOM" dans s, alors j'aurais besoin d'allouer de l'espace pour l'art. Je ferai * s = malloc (sizeof (char) * 5); Pourquoi 5? Pourquoi pas 4? Il ressemble à «BOOM» est de 4 caractères. >> [L'élève] Le caractère nul. Ouais. Toutes vos chaînes vont avoir besoin le caractère nul. Maintenant, je peux faire quelque chose comme strcat - Quelle est la fonction pour copier une chaîne de caractères? [L'élève] cpy? Strcpy >>. strcpy homme. Donc strcpy strncpy ou. strncpy est un peu plus sûr puisque vous pouvez spécifier exactement combien de caractères, mais ici ce n'est pas grave parce que nous savons. Donc strcpy et de regarder dans les arguments. Le premier argument est notre destination. Le second argument est notre source. Nous allons copier dans notre destination * s le pointeur "BOOM". Pourquoi auriez-vous envie de faire cela avec un strcpy au lieu de simplement ce que nous avions avant d'* s = "BOOM"? Il ya une raison pour laquelle vous voudrez peut-être le faire, mais quelle est cette raison? [L'élève] Si vous voulez changer quelque chose dans "BOOM". Ouais >>. Maintenant, je peux faire quelque chose comme s [0] = 'X'; parce que les points s sur le tas et que l'espace sur le tas qui s est orientée vers est un pointeur vers plus d'espace sur le tas, ce qui est le stockage "BOOM". Donc, cette copie de "BOOM" est stocké dans le tas. Il ya techniquement deux exemplaires de "BOOM" dans notre programme. Il est le premier qui vient donnée par cette constante "BOOM" chaîne de caractères, et la deuxième copie de "BOOM", strcpy crée la copie de "BOOM". Mais la copie de "BOOM" est stocké sur le tas, et le tas vous êtes libre de changer. Le tas est pas en lecture seule, ce qui signifie que s [0] va vous permettre de modifier la valeur de "BOOM". Il va vous permettre de modifier ces caractères. Des questions? D'accord. Passant à buggy3, nous allons de gdb buggy3. Nous venons de le faire fonctionner et nous voyons nous obtenons une erreur de segmentation. Si l'on trace, il ya seulement deux fonctions. Si nous remontons dans notre fonction principale, nous voyons que nous crashait sur cette ligne. Il suffit donc de regarder cette ligne, for (int ligne = 0; fgets ce genre de choses ne NULL n'est pas égale; ligne + +). Notre image précédente a été appelé _IO_fgets. Vous verrez que beaucoup de fonctions C intégrés, que lorsque vous obtenez l'erreur de segmentation, il y aura vraiment des noms de fonctions cryptiques comme ceci _IO_fgets. Mais cela va se rapporter à cet appel fgets. Quelque part à l'intérieur ici, nous sommes des erreurs de segmentation. Si nous regardons les arguments à fgets, nous pouvons imprimer tampon. Nous allons imprimer en tant que - Oh, non. D'impression n'est pas d'aller travailler exactement comme je veux. Regardons le programme proprement dit. Le tampon est un tableau de caractères. C'est un tableau de caractères de 128 caractères. Donc, quand je dis tampon d'impression, il va imprimer ces 128 caractères, qui je pense est ce qui est attendu. Qu'est-ce que je cherchais est d'imprimer l'adresse de tampon, mais cela ne me dit pas vraiment grand-chose. Donc, quand il m'arrive de dire ici x tampon, il me montre 0xbffff090, qui, si vous vous souvenez de plus tôt ou un certain point, Oxbffff tend à être une région pile-ish. La pile a tendance à commencer quelque part un peu moins de 0xC000. Juste en voyant cette adresse, je sais que le tampon qui se passe sur la pile. Le redémarrage de mon programme, courir, jusqu'à, tamponner nous avons vu était cette séquence de caractères qui sont à peu près vide de sens. Puis en imprimant le fichier, ce fichier ne ressemblerait-il? [L'élève] Null. Ouais >>. Le fichier est un des type FILE *, il est donc un pointeur, et la valeur de ce pointeur est nul. Donc fgets va essayer de lire à partir de ce pointeur d'une manière indirecte, mais dans le but d'accéder à ce pointeur, il doit le déréférencement. Ou, dans le but d'accéder à ce qu'il devrait être dirigée vers, il le déréférence. Donc, c'est le déréférencement d'un pointeur null et il segfault. J'aurais pu le redémarrage de là. Si nous rompons notre principal point de fuite, la première ligne de code est char * filename = "nonexistent.txt"; Cela devrait vous donner un indice assez grand pour expliquer pourquoi ce programme échoue. Taper prochaine m'amène à la ligne suivante, où je ouvrir ce fichier, et puis j'ai tout de suite entrer dans notre ligne, où une fois que j'ai touché prochaine, il va erreur de segmentation. Quelqu'un veut-il jeter une raison pour laquelle nous pourrions être des erreurs de segmentation? [L'élève] Le fichier n'existe pas. Ouais >>. Ceci est censé être un indice que chaque fois que vous ouvrez un fichier, vous devez vérifier que le fichier existe réellement. Donc, ici, "nonexistent.txt"; Lorsque nous avons fichier fopen pour la lecture, nous avons alors besoin de dire if (fichier == NULL) et dire printf ("Le fichier n'existe pas!" ou - mieux encore - filename); return 1; Alors maintenant, nous vérifions pour voir si elle est NULL avant même de poursuivre et d'essayer de lire ce fichier. Nous pouvons le refaire juste pour voir que cela fonctionne. J'ai l'intention d'inclure une nouvelle ligne. Alors maintenant, nonexistent.txt n'existe pas. Vous devriez toujours vérifier ce genre de chose. Vous devriez toujours vérifier pour voir si fopen retourne NULL. Vous devriez toujours vérifier pour s'assurer que malloc ne retourne pas NULL, ou bien vous segfault. Maintenant buggy4.c. Running. Je devine que cela est en attente d'entrée ou de boucle infinie peut-être. Oui, c'est une boucle infinie. Donc buggy4. Il semble que nous sommes en boucle infinie. Nous pouvons briser au principal, exécuter notre programme. Avec gdb, aussi longtemps que l'abréviation que vous utilisez est sans ambiguïté ou abréviations spéciales qu'elles offrent pour vous, alors vous pouvez utiliser n utiliser ensuite au lieu d'avoir à taper suivant tout le chemin. Et maintenant que j'ai frappé une fois n, je peux juste appuyer sur Entrée pour continuer prochaine au lieu d'avoir à frapper n Entrez, Entrez n, n Entrée. On dirait que je suis dans une sorte de boucle for qui est mise en tableau [i] à 0. On dirait que je ne suis jamais sortir de cette boucle for. Si je i imprimer, donc i vaut 2, alors je vais aller. Je vais imprimer i, i vaut 3, alors je vais aller. Je vais imprimer i et i est 3. Ensuite, j'ai l'impression, i est 4. En fait, l'impression sizeof (array), donc la taille de la matrice est de 20. Mais il semble qu'il y ait une certaine commande gdb spécial pour aller jusqu'à ce que quelque chose arrive. C'est comme mettre une condition sur la valeur de la variable. Mais je ne me souviens pas ce que c'est. Donc, si nous continuons - Que disiez-vous? Qu'avez-vous monter? [L'élève] Est-ce que je ajouter afficher - >> Oui. Donc afficher je peux aider. Si nous nous contentons i afficher, il sera mis en place voici ce que la valeur de i est donc je n'ai pas l'imprimer à chaque fois. Si nous just keep going suivant, nous voyons 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5. Quelque chose va terriblement mal, et i étant remis à 0. En regardant buggy4.c, nous voyons tout ce qui arrive est un tableau int [5]; for (i = 0; i <= sizeof (array); i + +) tableau [i] = 0; Que voyons-nous que c'est mal ici? Comme un soupçon, quand je faisais le gdb buggy4 - brisons principal, course - Je n'ai impression sizeof (array), juste pour voir ce que la condition est l'endroit où je devrais enfin éclater. Où suis-je? Ai-je courir? Je n'ai pas encore déclarer. Donc imprimer sizeof (array) et c'est 20, qui est attendue depuis mon tableau est de taille 5 et il est de 5 entiers, si la chose entière doit être de 5 * sizeof (int) octets, sizeof (int) tend à être 4. Donc, sizeof (array) est de 20. Que doit-il être? [L'élève] Divisé par sizeof (int). >> Ouais, / sizeof (int). Il semble qu'il y ait toujours un problème ici. Je pense que ce devrait juste être < car il est à peu près toujours > [Bowden] Oui. Lorsque nous allons au-delà de la fin de notre tableau, en quelque sorte que l'espace que nous la surcharge est la substitution de la valeur de i. Et si nous regardons dans buggy4, briser principal, courir, nous allons imprimer l'adresse de i. On dirait que c'est bffff124. Maintenant, nous allons imprimer l'adresse de tableau [0]. 110. Qu'en est-il [1]? 114. [2], 118. 11c, 120. array [5] est bfff124. Donc array [5] a la même adresse que i, ce qui signifie que array [5] i. S'ils ont la même adresse, ils sont la même chose. Donc, lorsque nous avons créé array [5] à 0, nous sommes i la mise à 0. Et si vous pensez à ce sujet dans termes de la pile, int i est déclarée en premier, ce qui signifie que i ait un peu d'espace sur la pile. Puis array [5] est affecté, de sorte alors 20 octets sont alloués sur la pile. Donc j'ai est alloué en premier, ces 20 octets se alloué. Donc, je passe juste avant le tableau, et à cause de la façon dont, comme je l'ai dit la semaine dernière, lorsque cela est techniquement la pile grandit vers le bas, lorsque vous indice dans un tableau, nous avons la garantie que la position 0e dans le tableau il arrive toujours avant la première position dans la matrice. C'est un peu de la façon dont je l'ai dessiné la semaine dernière. Remarquez qu'au fond nous avons l'adresse 0 et au sommet, nous avons Max adresse. La pile est toujours de plus en plus vers le bas. Disons que nous i allouer. Nous attribuons entier i, ce qui signifie Disons simplement ici entier i est alloué. Ensuite, nous attribuons notre tableau de 5 entiers, ce qui signifie qu'en dessous de ce que, depuis la pile est de plus en plus vers le bas, les 5 entiers se alloué. Mais à cause de la façon dont les tableaux travailler, nous garanti que la première position dans le tableau a toujours une adresse inférieure à la seconde chose dans le tableau. Ainsi, la position 0 tableau est toujours à cela arrive d'abord dans la mémoire, tandis que 1 position du tableau qui doit arriver après cela et 2 position du tableau qui doit arriver après cela, ce qui signifie que la position 0 éventail allait arriver quelque part ici-bas, 1 position large qui se passerait au-dessus de ce que car le déplacement en place des moyens plus élevés d'adresse depuis l'adresse maximale est ici. Donc array [0] ici-bas, array [1] ici, array [2] ici, array [3] ici. Remarquez comment nous avons attribué, avant entier i tout le chemin jusqu'à ici, que nous nous dirigeons de plus en plus dans notre tableau, nous nous rapprochons et plus proche de notre entier i. Il se trouve que array [5], qui est une position au-delà de notre gamme, C'est exactement là où je me trouvais entier à allouer. Donc, c'est le point où nous nous trouvons à frapper l'espace sur la pile qui a été alloué pour l'entier i, et que nous mettons en place à 0. C'est comme ça que fonctionne. Des questions? Ouais. [L'élève] Peu importe. D'accord. [L'élève] Comment pouvez-vous éviter ce genre de fautes? Ce genre d'erreurs? Ne pas utiliser C comme langage de programmation. Utilisez un langage qui doit limites du tableau de contrôle. Tant que vous êtes prudent, vous avez juste besoin d'éviter d'aller au-delà des limites de votre tableau. [L'élève] Donc ici quand nous sommes allés au-delà des limites de votre réseau - [Bowden] C'est là que les choses commencent à aller mal. >> [L'élève] Oh, d'accord. Tant que vous restez dans la mémoire allouée à votre tableau, vous êtes très bien. Mais C ne fait pas de vérification d'erreur. Si je fais tableau [1000], il se fera un plaisir il suffit de modifier quoi qu'il arrive - Il va au début du tableau, puis il va après 1000 positions et le met à 0. Il ne fait pas de vérification que oh, ce n'est pas vraiment 1000 choses en elle. 1000 est bien au-delà ce que je devrais changer, tandis que Java ou quelque chose que vous aurez baie hors de l'indice de limites ou indice sur des bornes exception. C'est pourquoi un grand nombre de langues de niveau supérieur ont ces choses où si vous allez au-delà des limites du tableau, vous ne de sorte que vous ne pouvez pas changer les choses par-dessous vous et puis les choses se passent bien pire que le simple fait d'exception dire que vous êtes allé au-delà de la fin du tableau. [L'élève] Et ne devrait donc nous avons juste changé le <= juste > [Bowden] Ouais. Il devrait être > [L'élève] Droit. Plus de questions? D'accord. [L'élève] J'ai une question. Ouais >>. [L'élève] Quelle est la variable tableau réel? [Bowden] Comme quoi est un tableau? Tableau lui-même est un symbole. Il est simplement l'adresse du début des 20 octets qui nous référencent. Vous pouvez penser que c'est un pointeur, mais il est un pointeur constant. Dès que les choses se compilée, la variable tableau n'existe plus. [L'élève] Alors, comment trouver la taille de tableau? Taille du tableau correspond à la taille de ce bloc qui se rapporte à ce symbole. Quand je fais quelque chose comme printf ("% p \ n", array); nous allons l'exécuter. Qu'est-ce que je viens de faire le mal? 'Array' tableau déclaré ici. Oh, là-haut. Clang est intelligent, et il arrive de constater que j'ai déclaré que le tableau 5 éléments mais je suis d'indexation en position 1000. Il peut le faire parce que ce ne sont que des constantes. Elle ne peut aller aussi loin en remarquant que je vais au-delà des limites du tableau. Mais remarquez avant lorsque nous avons eu i est incorrecte, cela ne peut pas déterminer le nombre de valeurs que je pourrais prendre, de sorte qu'il ne peut pas déterminer ce que j'allais au-delà de la fin du tableau. C'est juste Clang être intelligent. Mais maintenant faire buggy4. Alors quoi d'autre je fais de mal? Déclarer implicitement une fonction de bibliothèque 'printf'. Je vais vouloir # include. D'accord. Fonctionne maintenant buggy4. Impression de la valeur du tableau, comme je l'ai fait ici, l'imprimer comme un pointeur quelque chose de gravures qui ressemble à ceci - bfb8805c - ce qui est une adresse qui se trouve dans la zone de pile-ish. Tableau lui-même est comme un pointeur, mais ce n'est pas un pointeur réel, depuis un pointeur régulière, nous pouvons changer. Tableau est juste une constante. Les 20 blocs de mémoire commencent à 0xbfb8805c adresse. Donc bfb8805c via cette adresse +20- ou je suppose -20 - est toute la mémoire allouée pour ce tableau. Tableau, la variable elle-même n'est stocké nulle part. Lorsque vous compilez, le compilateur - vague main à elle - mais le compilateur ne se serviront où il sait être ensemble. Il sait où ce tableau commence, et donc il peut toujours juste faire les choses en termes de compensations de ce début. Il n'a pas besoin d'une variable pour représenter elle-même matrice. Mais quand je fais quelque chose comme int * p = array; maintenant p est un pointeur qui pointe vers ce tableau, et maintenant p existe bel et bien sur la pile. Je suis libre de changer p. Je peux faire p = malloc. Donc, il pointait initialement à la matrice, et maintenant il pointe un peu d'espace sur le tas. Je ne peux pas le faire = array malloc. Si Clang est intelligent, il va me crier dessus dès le départ. En fait, je suis sûr que gcc ne le font aussi. Ainsi type de tableau 'int [5] «n'est pas cessible. Vous ne pouvez pas assigner quelque chose à un type tableau parce tableau est juste une constante. C'est un symbole qui fait référence à ces 20 octets. Je ne peux pas le changer. [L'élève] Et où est la taille du tableau stocké? [Bowden] Ce n'est pas stocké n'importe où. C'est quand il a la compilation. Alors, où est la taille du tableau stocké? Vous ne pouvez utiliser sizeof (array) à l'intérieur de la fonction que le tableau est lui-même déclaré. Donc, si je fais une fonction, foo, et je le fais (int tableau []) printf ("% d \ n", sizeof (array)); et puis ici je appeler foo (array); l'intérieur de cette fonction - il faut bien l'exécuter. C'est Clang être intelligent à nouveau. Il me dit que sizeof le paramètre de la fonction réseau retourne la taille de la «int *». Ce serait une erreur si ce n'est pas ce que je voulais arriver. Nous allons effectivement éteindre Werror. Avertissement. Les avertissements sont très bien. Il faudra encore rassembler autant qu'il dispose d'une alarme. . / A.out va imprimer 4. L'avertissement qui a été généré est un indicateur clair de ce qui s'est passé. Ce tableau int va juste pour imprimer sizeof (int *). Même si je mets array [5] ici, c'est toujours aller juste pour imprimer sizeof (int *). Donc dès que vous passez dans une fonction, la distinction entre les tableaux et les pointeurs est inexistant. Cela arrive à être un tableau qui a été déclaré sur la pile, mais dès que nous passons de cette valeur, que 0xbf blah, blah, blah dans cette fonction, puis ce pointeur pointe sur cette matrice dans la pile. Cela signifie donc que sizeof ne s'applique que dans la fonction que le tableau a été déclaré, ce qui signifie que si vous compilez cette fonction, quand Clang passe par cette fonction, il voit tableau est un tableau de taille 5 int. Alors il voit sizeof (tableau). Eh bien, c'est 20. En fait, c'est la façon dont fonctionne essentiellement sizeof pour presque tous les cas. Sizeof n'est pas une fonction, c'est un opérateur. Vous n'appelez pas la fonction sizeof. Sizeof (int), le compilateur juste traduire à 4. Got it? D'accord. [L'élève] Alors, quelle est la différence entre sizeof (array) dans main et dans foo? C'est parce que nous disons, sizeof (array), qui est de type int *, tandis que le tableau vers le bas ici n'est pas de type int *, c'est un tableau int. [L'élève] Donc, si vous aviez le paramètre array [] au lieu d'un tableau int *, serait-ce à dire que vous pouvez toujours modifier tableau, car il est maintenant un pointeur? [Bowden] Vous aimez? >> [L'élève] Ouais. Pouvez-vous changer tableau dans la fonction maintenant? [Bowden] Vous pouvez changer de tableau dans les deux cas. Dans ces deux cas, vous êtes libre de dire array [4] = 0. [L'élève] Mais pouvez-vous faire le point ensemble à quelque chose d'autre? [Bowden] Oh. Ouais. Dans les deux cas - >> [l'élève] Ouais. [Bowden] La distinction entre array [] et un tableau de type int *, il n'en est pas. Vous pouvez également obtenir des tableaux multidimensionnels ici pour une syntaxe pratique, mais elle est encore juste un pointeur. Cela signifie que je suis libre de faire array = malloc (sizeof (int)), et maintenant pointer ailleurs. Mais tout comme la façon dont cela fonctionne toujours et à jamais, modifier ce tableau en le faisant pointer vers quelque chose d'autre ne change pas ce tableau ici-bas parce que c'est une copie de l'argument, ce n'est pas un pointeur sur cet argument. Et en fait, tout comme indication de plus que c'est exactement la même chose - nous avons déjà vu ce tableau impressions impression - si on imprimer l'adresse du réseau ou l'adresse de l'adresse du tableau soit à des personnes? Ignorons celui-ci. D'accord. C'est très bien. Il tourne maintenant. / A.out. Ensemble d'impression, puis imprimer l'adresse du tableau, c'est la même chose. Tableau n'existe tout simplement pas. Il sait quand vous imprimez tableau, vous imprimez le symbole qui fait référence à ces 20 octets. Imprimer l'adresse du tableau, ainsi, tableau n'existe pas. Il ne possède pas d'adresse, il affiche juste l'adresse de ces 20 octets. Dès que vous compilez le bas, comme dans votre buggy4 compilé. / A.out, tableau est inexistante. Pointeurs existent. Les tableaux ne sont pas. Les blocs de mémoire représentant l'ensemble existent encore, mais le tableau de variables et les variables de ce type n'existent pas. Ce sont comme les principales différences entre les tableaux et les pointeurs sont dès que vous effectuer des appels de fonction, il n'ya aucune différence. Mais à l'intérieur de la fonction que le tableau lui-même est déclarée, sizeof fonctionne différemment puisque vous l'impression de la taille des blocs au lieu de la taille du type, et vous ne pouvez pas le changer parce que c'est un symbole. Impression de la chose et l'adresse de la chose imprime la même chose. Et c'est à peu près tout. [L'élève] Pourriez-vous dire que une fois de plus? J'ai peut-être raté quelque chose. Ensemble d'impression et l'adresse du tableau affiche la même chose, tandis que si vous imprimez un pointeur par rapport à l'adresse du pointeur, la seule chose imprime l'adresse de ce que vous pointez, l'autre imprime l'adresse du pointeur sur la pile. Vous pouvez modifier un pointeur, vous ne pouvez pas changer un symbole de tableau. Et pointeur sizeof va imprimer la taille de ce type de pointeur. Donc, int * p sizeof (p) va imprimer 4, mais int array [5] impression sizeof (array) va imprimer 20. [L'élève] Donc int array [5] permet d'imprimer 20? Oui >>. C'est pourquoi l'intérieur de buggy4 quand il l'habitude d'être sizeof (array) cela a été fait i <20, ce qui n'est pas ce que nous voulions. Nous voulons que i <5. >> [L'élève] D'accord. [Bowden] Et puis, dès que vous commencez à passer dans les fonctions, si nous le faisions int * p = array; l'intérieur de cette fonction, on peut utiliser fondamentalement p et rangée dans exactement de la même manière, à l'exception du problème sizeof et le problème de changer. Mais p [0] = 1; est la même chose que dire array [0] = 1; Et dès que nous disons foo (array); ou foo (p); à l'intérieur de la fonction foo, c'est le même appel deux fois. Il n'y a pas de différence entre ces deux appels. Tout le monde bien sur cela? D'accord. Nous avons 10 minutes. Nous allons essayer de passer à travers ce programme Typer Hacker, ce site, qui est sorti l'année dernière ou quelque chose. C'est juste censé être comme vous tapez au hasard et l'imprime - Quel que soit le fichier, il arrive à avoir chargé est ce qu'il semble que vous tapez. Il ressemble à une sorte de code du système d'exploitation. C'est ce que nous voulons mettre en œuvre. Vous devriez avoir un fichier binaire exécutable nommé hacker_typer qui prend en un seul argument, le fichier «type hacker». Exécution de l'exécutable doit effacer l'écran et ensuite imprimer un caractère à partir du fichier passé en entrée à chaque fois que l'utilisateur appuie sur une touche. Donc, quoi que vous appuyez sur la touche, il faut jeter et au lieu d'imprimer un caractère à partir du fichier c'est l'argument. Je vais assez bien vous dire ce que les choses que nous allons avoir besoin de connaître sont. Mais nous voulons aller à votre bibliothèque termios. Je n'ai jamais utilisé cette bibliothèque dans toute ma vie, donc il a des objectifs très minimes. Mais cela va être la bibliothèque nous pouvons utiliser pour jeter le personnage que vous a frappé lorsque vous tapez dans pouces standard Donc hacker_typer.c, et nous allons vouloir # include. En regardant la page de manuel de termios - je devine que le terminal OS ou quelque chose - Je ne sais pas comment le lire. En regardant cela, il dit d'inclure ces 2 fichiers, de sorte que nous allons faire. La première chose d'abord, nous voulons prendre en un seul argument, qui est le fichier qu'il faut ouvrir. Alors qu'est-ce que je veux faire? Comment puis-je vérifier pour voir que j'ai un seul argument? [L'élève] Si elle est égale à argc. >> [Bowden] Ouais. Donc if (argc = 2!) Printf ("usage:% s [fichier à ouvrir]»). Alors maintenant, si je lance ce sans fournir un second argument - oh, j'ai besoin de la nouvelle ligne - vous verrez qu'il dit utilisation:. / hacker_typer, puis le second argument doit être le fichier que je veux ouvrir. Maintenant que dois-je faire? Je veux lire ce fichier. Comment puis-je lire un fichier? [L'élève] Vous d'abord l'ouvrir. Ouais >>. Ainsi, la fonction fopen. Qu'est-ce que la fonction fopen ressembler? [L'élève] Nom de fichier. >> [Bowden] Nom du fichier va être argv [1]. [L'élève] Et puis ce que vous voulez faire avec elle, de sorte que le - >> [Bowden] Ouais. Donc, si vous ne me souviens pas, vous pouvez simplement faire fopen homme, où il va y avoir un chemin const char * nom du fichier où chemin est, const char * mode d'. S'il vous arrive de ne pas se souvenir de ce mode est, alors vous pouvez chercher pour le mode. A l'intérieur des pages de manuel, la barre oblique est ce que vous pouvez utiliser pour rechercher des choses. Donc je tape / mode à la recherche de mode. n et N sont ce que vous pouvez utiliser pour faire défiler les matchs de recherche. Ici, il dit que les argument mode pointe vers une chaîne en commençant par l'une des séquences suivantes. Donc r, fichier texte ouvert pour la lecture. C'est ce que nous voulons faire. Pour la lecture, et je veux qu'elle soit stockée. La chose va être un FILE *. Maintenant, qu'est-ce que je veux faire? Donnez-moi une seconde. D'accord. Maintenant, qu'est-ce que je veux faire? [L'élève] Vérifiez si c'est NULL. >> [Bowden] Ouais. Chaque fois que vous ouvrez un fichier, assurez-vous que vous êtes en mesure de réussir l'ouvrir. Maintenant, je veux faire ce genre de choses termios où je veux d'abord lire mes paramètres actuels et sauver ceux en quelque chose, alors je veux modifier mes réglages de jeter n'importe quel caractère de type I, et puis je veux mettre à jour ces paramètres. Et puis à la fin du programme, je veux revenir à mes paramètres d'origine. Ainsi, la structure va être de type termios, et je vais vouloir deux d'entre eux. Le premier va être mes current_settings, et puis ils vont être mes hacker_settings. Tout d'abord, je vais souhaitez enregistrer mes paramètres actuels, alors je vais mettre à jour hacker_settings, puis cours à la fin de mon programme, je veux revenir aux paramètres actuels. Ainsi, l'enregistrement des paramètres actuels, la façon dont cela fonctionne, nous termios homme. Nous voyons que nous avons cette tcsetattr int, int tcgetattr. Je passe dans une structure termios par son pointeur. La façon dont cela va regarder est - je l'ai déjà oublié ce que la fonction a été appelée. Copiez et collez. Donc tcgetattr, alors je veux faire passer dans la structure que je suis d'enregistrer les informations dans, qui va être current_settings, et le premier argument est le descripteur de fichier pour la chose que je veux enregistrer les attributs de. Ce que le descripteur de fichier est est comme chaque fois que vous ouvrez un fichier, il reçoit un descripteur de fichier. Quand je fopen argv [1], il reçoit un descripteur de fichier qui vous référencez chaque fois que vous voulez lire ou écrire. Ce n'est pas un descripteur de fichier que je veux utiliser ici. Il ya trois descripteurs de fichiers que vous avez par défaut, qui sont la norme dans, la sortie standard et l'erreur standard. Par défaut, je pense que c'est la norme en est 0, la sortie standard est de 1, et de l'erreur-type vaut 2. Alors qu'est-ce que je veux changer les paramètres de? Je veux changer les paramètres de chaque fois que je touché un caractère, Je veux lancer ce caractère de suite au lieu de l'imprimer à l'écran. Que flux - standard, la sortie standard ou d'erreur standard - répond à des choses quand je tape au clavier? >> [L'élève] Standard po >> Oui. Donc, je peux soit faire 0 ou je peux faire stdin. Je reçois le current_settings des pouces standard Maintenant, je veux mettre à jour ces paramètres, si d'abord je vais copier dans hacker_settings ce que mes current_settings sont. Et comment le travail est structs il suffit de copier. Cela permet de copier tous les champs, comme vous le souhaitiez. Maintenant, je veux mettre à jour certains champs. En regardant termios, vous devez lire beaucoup de ce juste pour voir ce que vous voulez chercher, mais les drapeaux que vous allez avoir à rechercher sont l'écho, si ECHO saisir les caractères Echo. Je veux d'abord régler - je l'ai déjà oublié ce que les champs sont. C'est ce que la structure ressemble. Ainsi, les modes d'entrée, je pense que nous voulons changer. Nous allons examiner la solution pour vous assurer que c'est ce que nous voulons changer. Nous voulons changer lflag afin de prévenir besoin de regarder à travers tout cela. Nous voulons changer les modes locaux. Vous n'avez pas à lire toute cette chose à comprendre où tout appartient que nous voulons changer. Mais c'est à l'intérieur des modes locaux où nous allons voulons changer cela. Donc hacker_settings.cc_lmode est ce que ça s'appelle. c_lflag. C'est là que nous entrons dans des opérateurs de bits. Nous sommes un peu hors du temps, mais nous allons passer par là très vite. C'est là que nous entrons dans des opérateurs de bits, où je crois que j'ai dit une fois il ya longtemps que chaque fois que vous commencer à traiter avec des drapeaux, vous allez être en utilisant l'opérateur bit à bit beaucoup. Chaque bit dans le drapeau correspond à une sorte de comportement. Donc, ici, ce drapeau a un tas de choses différentes, où chacun d'eux signifie quelque chose de différent. Mais ce que je veux faire est de simplement désactiver le bit qui correspond à ECHO. Donc, pour éteindre ça que je fais et = ¬ ECHO. En fait, je pense que c'est comme Techo ou quelque chose. Je vais vérifier de nouveau. Je peux le termios. C'est juste ECHO. ECHO va être un peu seul. ¬ ECHO va dire tous les bits sont mis à 1, ce qui signifie que tous les drapeaux sont définies sur true à l'exception du bit ECHO. En mettant fin à mes drapeaux locales avec cela, cela signifie que tous les drapeaux qui sont actuellement définies sur true sera toujours la valeur true. Si mon drapeau ECHO est définie sur true, cela est nécessairement définie sur false sur le drapeau ECHO. Donc cette ligne de code tourne juste à côté du drapeau ECHO. Les autres lignes de code, je vais les copier dans l'intérêt de temps et puis les expliquer. Dans la solution, at-il dit 0. Il est probablement mieux de dire explicitement stdin. Remarquez que je fais aussi ECHO | ICANON ici. ICANON se réfère à quelque chose de séparé, ce qui signifie que le mode canonique. Quels sont les moyens mode canonique est habituellement quand vous tapez sur la ligne de commande, norme ne traite pas de rien jusqu'à ce que vous appuyez sur une nouvelle ligne. Ainsi, lorsque vous ne GetString, vous tapez un tas de choses, alors vous frappez saut de ligne. C'est quand il est envoyé à la norme po C'est la valeur par défaut. Lorsque je désactive le mode canonique, maintenant chaque personnage que vous appuyez sur est ce qui doit être traitée, ce qui est généralement un peu mal parce que c'est lent à traiter ces choses, C'est pourquoi il est bon de mettre en mémoire tampon dans les lignes entières. Mais je veux que chaque personnage à traiter car je ne voulez pas attendre pour moi de frapper saut de ligne avant de traiter tous les personnages que j'ai tapé. Cela désactive le mode canonique. Ce truc signifie simplement que quand il traite en fait des personnages. Cela signifie les traiter immédiatement, dès que je les taper, de les traiter. Et c'est la fonction qui est mise à jour mes paramètres pour standard, et des moyens TCSA le faire dès maintenant. Les autres options sont attendre que tout ce qui est actuellement sur le flux est traité. Cela n'a pas vraiment d'importance. Juste ce qu'il faut maintenant modifier les paramètres de mon être tout ce qui est actuellement en hacker_typer_settings. Je suppose que je l'ai appelé hacker_settings, nous allons donc changer cela. Changer tout pour hacker_settings. Maintenant, à la fin de notre programme, nous allons souhaitez revenir à ce qui est actuellement à l'intérieur de normal_settings, qui va juste ressembler et normal_settings. Remarquez que je n'ai pas changé aucun de mes normal_settings depuis l'origine de l'obtenir. Ensuite, il suffit de les changer à dos, je les passe en arrière à la fin. Il s'agissait de la mise à jour. D'accord. Maintenant, à l'intérieur d'ici, je vais vous expliquer le code dans l'intérêt du temps. Ce n'est pas que beaucoup de code. Nous voyons que nous lisons un caractère à partir du fichier. Nous l'avons appelé f. Maintenant, vous pouvez homme fgetc, mais comment fgetc est d'aller travailler est juste que ça va revenir le personnage que vous venez de lire, ou EOF, ce qui correspond à la fin du fichier ou d'un événement d'erreur. Nous sommes en boucle, en continuant à lire un seul caractère dans le fichier, jusqu'à ce que nous n'avons plus de caractères à lire. Et tandis que nous le faisons, nous nous attendons à un seul caractère pouces standard Chaque fois que vous tapez quelque chose dans la ligne de commande, qui est lu dans un personnage de pouces standard Puis putchar va juste mettre le char nous lisons ici à partir du fichier de sortie standard. Vous pouvez homme putchar, mais il est juste de mettre sur la sortie standard, c'est l'impression de ce caractère. Vous pouvez aussi simplement faire printf ("% c", c); même idée. Cela va faire le gros de notre travail. La dernière chose que nous allons avoir à faire est juste fclose notre fichier. Si vous n'avez pas fclose, c'est une fuite de mémoire. Nous voulons le fichier fclose nous avait ouvert ses portes, et je pense que c'est ça. Si nous faisons cela, j'ai déjà eu des problèmes. Voyons voir. Que s'est-il se plaindre? Prévue 'int', mais l'argument est de type 'struct _IO_FILE *'. Nous allons voir si cela fonctionne. Uniquement autorisé dans C99. Augh. Bon, faire hacker_typer. Maintenant nous avons des descriptions plus utiles. Il faut donc utiliser l'identificateur non déclaré »normal_settings». Je n'ai pas l'appeler normal_settings. Je l'ai appelé current_settings. Donc, nous allons changer tout cela. Maintenant, passer argument. Je vais faire ce 0 pour le moment. D'accord. . / Hacker_typer cp.c. Mais je n'ai pas l'écran au début. Mais vous pouvez regarder en arrière au problème posé dernière pour voir comment vous effacer l'écran. C'est juste l'impression de certains caractères tout cela est de faire ce que je veux faire. D'accord. Et penser à pourquoi cela devait être 0 au lieu de stdin, qui devrait être # define 0, ce qui se plaint - Avant, quand je dis qu'il ya descripteurs de fichiers, mais vous avez aussi votre FILE *, un descripteur de fichier est juste un seul entier, tandis qu'un FILE * a tout un tas de trucs qui lui est associé. La raison pour laquelle nous avons besoin de dire 0 au lieu de stdin est que stdin est un FILE * qui pointe vers la chose qui fait référence descripteur de fichier 0. Ainsi, même ici quand je fais fopen (argv [1], je me fais un FILE * en arrière. Mais quelque part dans ce FILE * est une chose qui correspond à un descripteur de fichier pour ce fichier. Si vous regardez la page de manuel ouvert, donc je pense que vous aurez à faire l'homme ouvert 3 - nope - man 2 ouvert - oui. Si vous regardez la page pour ouvrir, ouverte, c'est comme un fopen niveau inférieur, et il est de retour le descripteur de fichier réel. fopen fait un tas de trucs sur le dessus de normes ouvertes, qui, au lieu de retourner tout ce descripteur de fichier renvoie un pointeur sur l'image entière * à l'intérieur de laquelle est notre petit descripteur de fichier. Donc standard se réfère à la chose FILE *, alors que 0 désigne simplement la norme descripteur de fichier en lui-même. Des questions? [Rires] Blew par là. Très bien. Nous en avons terminé. [Rires] [CS50.TV]