0
votes

allouer un vecteur flottant avec une plage d'indice v [nl..nh]

Ok, donc je veux comprendre ce que cela fait:

int main()
{
    printf("Hello world\n\n");

    float* f = vector(3,5);

    f[0]=1;
    f[1]=2;
    f[2]=3;
    f[7] = 5;


    printf("%f", f[7]);

    return 0;

}

Je veux dire, cela crée un pointeur vers un espace malloc de la taille qui y est donnée. donc v [0] est la première unité ... et ainsi de suite jusqu'à la fin du bloc.

mais pourquoi un terme est-il retourné à la fin.

Je pensais que vous ne pouviez pas Tu ne bouges nulle part sur la pile avec un pointeur mais tu peux juste accéder à l'espace donné?

#define NR_END 1

float *vector(long nl, long nh)
/* allocate a float vector with subscript range v[nl..nh] */
{
    float *v;

    v=(float *)malloc((size_t) ((nh-nl+1+NR_END)*sizeof(float)));
    if (!v) nrerror("allocation failure in vector()");
    return v-nl+NR_END;
}

J'ai fait ces tests et il compile sans erreurs ni avertissements lorsque gcc filename.c -o filename.

et pourquoi v [7] = 5 fonctionne quand il ne devrait pas?

et qu'est-ce qu'une plage d'indice?

et comment êtes-vous censé l'utiliser correctement?

je suis très confus . Aidez-moi à comprendre cela.

le fichier semble également visible à https://www.cfa.harvard.edu/~sasselov/rec/code/nrutil.c


13 commentaires

Notez que C et C ++ sont deux langages très différents, avec des règles et une sémantique différentes. En tant que tel, veuillez vous abstenir d'utiliser des termes comme "C / C ++" ou de marquer les deux langues dans vos questions. C'est pourquoi la balise C ++ a été supprimée, car elle ne semble pas applicable (car vous programmez en C).


Quant à une partie de votre problème. C ne dispose d'aucune sorte de vérification des limites. Il est de votre responsabilité en tant que programmeur de ne pas sortir des limites de la mémoire allouée. Sortir des limites conduit à un comportement indéfini .


Est-ce que cela répond à votre question? À quel point il est dangereux d'accéder à un tableau hors de limites?


@someprogrammerdude J'étais au courant de cela, mais j'ai pensé qu'avec le pointeur, vous pouviez déplacer une seule unité en dehors de l'espace donné et c'est tout. Sinon, cela donnerait une erreur ou un smth. Je veux dire que vous pourriez juste pointer quelque part et que réécrire comme 1000 unités à venir comme * (p + 1000) = 500 ? je ne pense pas que cela fonctionne.


@kaylum no. Je veux que quelqu'un m'explique exactement ce que fait la fonction ci-dessus.


Nous vous avons dit ce qu'il fait et le lien aussi. C'est un comportement indéfini à cause des accès hors limites. Ce qui signifie que nous ne pouvons pas vous dire avec certitude quel sera le résultat.


@kaylum je veux dire ce que fait vector () et comment l'utiliser. cette fonction provient d'une bibliothèque. je ne l'ai pas écrit. Je veux dire que ce retourne v-nl + NR_END; serait également un comportement indéfini puisque v commence à 0, n'est-ce pas?


Qu'est-ce que NR_END ? Il semble que cela soit critique pour déterminer quelle sera la valeur renvoyée de la fonction vector - vraisemblablement, elle sera toujours > nl , donc la valeur de retour calculée ne sera jamais inférieure à v .


et quelle est cette "plage d'indice"?


NR_END est juste 1, ceci a été écrit en haut du fichier #define NR_END 1 @adrianmole


le fichier semble également être en ligne. cfa.harvard.edu/~sasselov/rec/code/nrutil. c


Je ne sais pas quel est le point, mais il renvoie un tableau avec des indices valides entre nl et nh au lieu des indices habituels basés sur 0. Autrement dit, par exemple, f [0] n'est pas un accès mémoire valide à moins que nl = 0.


@AndiHamolli une plage d'indices est quelque chose qui vous permet d'accéder à un tableau avec des plages d'indices autres que [0..n] par exemple [5..10]. Alors array [5] serait le premier élément et array [10] serait le dernier élément. Cependant, en C, ce concept n'existe pas, mais la fonction vector de votre code essaie de l'émuler et échoue (voir diverses réponses ci-dessous).


3 Réponses :


3
votes

C'est un code assez sommaire, ce n'est pas une bonne idée.

La fonction semble allouer un vecteur où l'appelant promet de n'utiliser que les index dans l'intervalle fermé [nl , nh], par exemple vector (100, 104) essaierait d'allouer un vecteur à 5 éléments indexés 100, 101, 102, 103 et 104. Tout autre index entraîne un comportement indéfini. Cela inclut 0 qui est généralement le premier index valide d'un tableau C.

Les deux lignes de code principales sont:

   +---+---+---+---+---+---+
v: | 0 | 1 | 2 | 3 | 4 | 5 |
   +---+---+---+---+---+---+

Cela peut être réécrit un peu plus proprement sans les casts inutiles:

// 2
return v - nl + NR_END;

Ceci calcule alors la taille de l'intervalle d'index souhaité ( nh - nl + 1 donnerait 104 - 100 + 1 qui vaut 5 dans notre exemple). Il ajoute des éléments NR_END supplémentaires, je ne sais pas pourquoi.

Et puis il y a:

v = malloc((nh - nl + 1 + NR_END) * sizeof *v);

Cela renvoie le pointeur de base v , mais l'ajuste d'abord en reculant les éléments nl , c'est-à-dire 100 dans notre exemple. Cela provoque l'indexation par 100 pour atteindre le premier élément alloué réel. L'ajout de NR_END biaise l'utilisation du vecteur alloué vers la fin, encore une fois, je n'ai aucune idée de pourquoi.

Donc, en mémoire, cela ressemblerait à ceci, avec 6 éléments alloués:

// 1
v=(float *)malloc((size_t) ((nh-nl+1+NR_END)*sizeof(float)));

Mais en soustrayant, nous renvoyons un pointeur vers les adresses inférieures, en nous appuyant sur l'indexation de l'appelant avec au moins nl pour sauvegarder dans l'espace alloué .

Cela dit, je suis à peu près sûr que ce comportement n'est pas défini et suppose un peu beaucoup de calculs d'adresses. Vous n'êtes pas censé travailler avec des adresses en dehors de la plage [0, N] pour un tableau de N éléments, ce qui exclurait de traiter les adresses avant le début du tableau.


2 commentaires

Excellente réponse! Comme vous, je ne sais pas non plus pourquoi l'élément supplémentaire (NR_END) est ajouté. Aussi, je pense que cela vient de la version "Traditional K&R" du livre / code "Numerical Recipes in C" - très vieux et sujet à beaucoup d'abus.


oh, alors il renvoie volontairement un mauvais pointeur (est-ce vraiment une bonne pratique ou y a-t-il une meilleure façon de le faire?) de sorte que lorsque vous faites * (p + nl) vous atterrissez réellement endroit correct où vous devriez avoir. Le NR_END n'a pas vraiment de sens, il fonctionnerait sans cela, je pense. mais comme il l'utilise au retour, je pense qu'il laisse le premier élément du tableau créé vide, et vous accédez au [101] ème index, c'est-à-dire le [2] ème index si vous faites v [100] et non le Premier?



1
votes

Ce que fait la fonction vector (long nl, long nh) est de créer un tableau de la taille nécessaire pour contenir la plage de nombres donnée, et d'ajouter un élément 'extra' (éventuellement pour permettre la pratique C de prendre une adresse «au-delà des limites»). Donc, si vous l'appelez avec des valeurs de 3 et 5 , il alloue de l'espace pour 4 float variables à l'adresse dans v .

Cependant, en utilisant simplement cette adresse v , il faudrait accéder aux éléments en utilisant des index dans la plage 0 à 2 . Ainsi, avant de renvoyer une adresse, la fonction en soustrait votre index inférieur ( nl ). Maintenant, bien que cette valeur pointe désormais vers une adresse mémoire invalide, si vous n'accédez qu'aux éléments de votre plage spécifiée, alors l'arithmétique du pointeur utilisée dans le calcul, dites f [3] dans votre main , ajoutera un décalage approprié à ce pointeur "invalide", de sorte que vous soyez alors effectivement accédant à la variable v [0] .


3 commentaires

voulez-vous dire de 0 à 3, car il y a cet élément supplémentaire dans le tableau?


Oui et non! Je ne comprends pas complètement pourquoi l’élément supplémentaire est ajouté mais, en s’en tenant à la plage demandée, les index de 3… 5 «équivaudraient» à 0… 2 où je le mentionne dans ma réponse.


je pensais peut-être qu'il laisse un élément supplémentaire dans le tableau, car il renvoie smth + NR_END, ce qui signifie que lorsque vous avez un vecteur (100,104) et que vous faites v [100], vous allez en fait à la deuxième place dans le tableau créé par malloc. ce qui signifie que si vous faites v [99], ce serait également correct et accéderait au premier élément du tableau, ce qui n'a aucun sens pourquoi vous voudriez que



1
votes

Pour voir que le code que vous avez écrit échoue réellement, vous devez faire plus que simplement compiler le code sans aucun indicateur supplémentaire. Si vous utilisez clang MemorySantizer et désactivez les optimisations du compilateur, vous verrez qu'il y a un problème avec l'accès mémoire effectué avec f [0] .

C ne vérifie pas les limites et laisse le soin au programmeur. La fonction vector () renvoie un emplacement mémoire qui ne peut être adressé que via v [nl] ... v [nh]

Ligne 23 dans le ac est f[0ITED=1;

% clang -fsanitize=memory -fno-omit-frame-pointer -g -O0 a.c
% ./a.out 
Hello world

MemorySanitizer:DEADLYSIGNAL
==26321==ERROR: MemorySanitizer: SEGV on unknown address 0x600ffffffff8 (pc 0x0000010a9670 bp 0x7fffffffeaa0 sp 0x7fffffffea10 T101363)
==26321==The signal is caused by a WRITE memory access.
    #0 0x10a966f in main /home/fnoyanisi/a.c:23:9
    #1 0x1060b5a in __sanitizer::ReportDeadlySignalImpl(__sanitizer::SignalContext const&, unsigned int, void (*)(__sanitizer::SignalContext const&, void const*, __sanitizer::BufferedStackTrace*), void const*) /usr/src/contrib/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_report.cc:211:3
    #2 0x1060b5a in __sanitizer::ReportDeadlySignal(__sanitizer::SignalContext const&, unsigned int, void (*)(__sanitizer::SignalContext const&, void const*, __sanitizer::BufferedStackTrace*), void const*) /usr/src/contrib/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_report.cc:225
    #3 0x1060e8e in __sanitizer::HandleDeadlySignal(void*, void*, unsigned int, void (*)(__sanitizer::SignalContext const&, void const*, __sanitizer::BufferedStackTrace*), void const*) /usr/src/contrib/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_report.cc:234:3

MemorySanitizer can not provide additional info.
SUMMARY: MemorySanitizer: SEGV /home/fnoyanisi/a.c:23:9 in main
==26321==ABORTING
% sed '23q;d' a.c
    f[0]=1;
% clang a.c
% ./a.out 
Hello world

5.000000
% 


7 commentaires

qu'en est-il de f [1] et f [2] et f [7]? comment puis-je faire en sorte que gcc me montre des avertissements. Je ne comprends pas vraiment ce que ce% est réellement?


% est mon invite ( tcsh ), l'exemple provient d'une machine FreeBSD (pour une raison quelconque, clang MemorySanitizer est cassé dans macOS). Je ne sais pas si gcc a des outils comme MemorySanitizer mais si vous êtes sur gnu / Linux, vous pouvez essayer d'utiliser -O0 pour compiler et utiliser Valgrind peut-être?


et pourquoi ne montre-t-il pas l'erreur pour f2 et f7? quel est ce truc clang?


pouvez-vous m'expliquer ce que dit exactement votre erreur?


Il n'affiche pas d'erreur pour f [2] ou f [7] car le programme se ferme déjà lorsqu'une tentative d'écriture de mémoire pointée par f [0 ] est fait ... si vous demandez ce qu'est le bruit et postez une question de programmation C ... eh bien, c'est comme conduire une voiture et dire ce qu'est une Mercedes ... < a href = "https://en.wikipedia.org/wiki/Clang" rel = "nofollow noreferrer"> en.wikipedia.org/wiki/Clang


existe-t-il un moyen d'afficher tous les avertissements possibles lors de l'utilisation de gcc. je pense que quelque chose comme ça, devrait au moins apparaître comme un avertissement lorsque vous faites f [0]


Pour voir tous les avertissements, vous pouvez utiliser -Wall. Mais comme je l'ai dit, votre compilateur ne vérifie pas les limites et s'attend à ce que vous le fassiez. En cas de tentative d'accès à la mémoire hors de votre plage, vous obtiendrez une erreur d'exécution (SIGSEGV) plutôt qu'un avertissement / une erreur de compilation. Clang MemorySanitazer effectue des vérifications d'exécution, tout comme Valgring