Jump to content

problème de tableau en C


Recommended Posts

Bonjour à tous,

Je suis grand débutant en C (et en programmation tout court) et j'aurais besoin de votre aide. Le but de l'exo posé était de calculer les 20 premiers termes d'une intégrale (E[n]=int(x^n*exp(x-1),x,0,1)) à l'aide de 2 récurrences (E[n]=1-n*E[n-1] et E[n-1]=(1-E[n])/n) et de calculer l'écart par rapport aux vraies valeurs fournies dans un fichier annexe.

Le but était de nous faire utiliser les tableaux et les boucles. Seulement, j'ai créé un tableau qui ne peut pas marcher:

#include<stdio.h>
#include<math.h>
#define TAILLE (20)

double		  tab[TAILLE];
double		  ref[TAILLE];
double		  exact[TAILLE];
double		  c[TAILLE];
double		  d[TAILLE];
int			 i;
FILE		   *f;

main()
{
	tab[1] = 1 / (M_E);
	exact[20] = 0;
	f = fopen("TD2_valeurs_exactes.dat", "r");
	if (f == NULL) {
			printf("ERREUR FICHIER f");
			return (-1);
	}
	for (i = 1; i <= TAILLE; i++) {
			tab[i + 1] = 1 - (i + 1) * tab[i];
			fscanf(f, "%lf", &ref[i]);
			c[i - 1] = tab[i] - ref[i];

	}
	for (i = 20; i > 0; i--) {
			exact[i - 1] = (1 - exact[i]) / i;
	}
	for (i = 1; i <=TAILLE; i++) {
			printf("E[%d]=%e\n", i, tab[i]);
			printf("Par la 2ème méthode E[%d]=%e\n", i, exact[i]);
			printf("La valeur exacte est E[%d]=%e\n", i, ref[i]);
			d[i - 1] = exact[i] - ref[i];
			if (ref[i] != tab[i]) {
					if (exact[i] != ref[i]) {
							printf("La 1ère méthode est erronée, la variation est de %e\nLa 2ème méthode est erronée, la variation est de %e\n\n", c[i - 1], d[i - 1]);
					} else {
							printf("La 1ère méthode est erronée, la variation est de %e\nLa 2ème méthode est correcte\n\n", c[i - 1]);
					}
			} else {
					if (exact[i] != ref[i]) {
							printf("La 1ère méthode est correcte, la variation est de %e\nLa 2ème méthode est erronée, la variation est de %e\n\n", d[i - 1]);
					} else {
							printf("La 1ère méthode est correcte, \nLa 2ème méthode est correcte\n\n");
					}
			}
	}
	fclose(f);
	return (0);
}

Comme on peut le voir, j'écris en dehors du tableau ce qui entraine normalement un joli "erreur de segmentation" lors de l'exécution. Oui mais lorsque je lance le prog depuis la fac tout roule comme sur des roulettes. Les machines utilisées tournent sous Solaris 9 avec GCC 3.2 (pas sur). Le plus bizarre c'est qu'en changeant la valeur de sortie de i dans les boucles for le programme tourne encore. Mais j'ai essayé ce soir chez moi sur une mandrake 2006 avec GCC 4.0.1 et cette fois j'ai eu une erreur à l'exécution.

Dans les boucles, je suis obligé de définir les variations c et d en (i-1) sinon j'ai une erreur sur les 2 machines.

Voilà si quelqu'un peut m'expliquer mon erreur (l'écriture en dehors du tableau) ou me donner un bon lien qu'il a sous la main expliquant le problème.

Merci d'avance :reflechis:

PS: je suis en licence de physique et le C est juste un outil qui va nous permettre de résoudre des problèmes de physique donc c'est pas le plus important dans mon cursus mais j'ai quand meme pas envie de foirer le semestre pour ça.

Link to comment
Share on other sites

Ho ! En C un tableau commence à l'indice 0 !!! Donc dans ton cas, TAILLE vaut 20, tu as donc 20 cases numérotées de 0 à 19. Donc:

for(i=0; i<TAILLE; i++)

Concernant ton programme qui marche parfois, je pense que c'est dû au fait que la gestion de la mémoire est plus laxiste sur certains systèmes que d'autres (on peut "taper" dans des zones non allouées).

Link to comment
Share on other sites

Ok merci pour la réponse. Ca m'apprendra à vouloir prendre un raccourci.

Pour le système laxiste, solaris c'est quand meme de l'unix, ça devrait gérer correctement la mémoire non? Ou c'est peut-etre une option qui se désactive pour éviter que le controleur ne se transforme en goulet d'étranglement (on a juste accès à l'écran sun relié en réseau au serveur, donc ça fait du monde en meme temps sur le meme matériel).

Link to comment
Share on other sites

Je suis sous Kubuntu 5.10 avec GCC 4.0.2.

#include <stdio.h>
#include <stdlib.h>

int main()
{
 int taille=5,i;
 int tab[taille];
 for(i=0;i<=taille;i++)
tab[i]=i;
 for(i=0;i<=taille;i++)
printf("%d: %d\n",i,tab[i]);
 return EXIT_SUCCESS;
}

Ce code m'affiche:

0: 0

1: 1

2: 2

3: 3

4: 4

5: 5

Ce qui fait bien 6 cases... Comme quoi, on peut quand même faire n'importe quoi :)

Link to comment
Share on other sites

Celà dépend du chargement du programme en mémoire ...

Lorsqu'un programme est chargé en mémoire, une certaine zone mémoire lui est alloué pour les variables utilisées dans le programme (heap).

Si tu vas "taper" en dehors de cette zone, tu "seg fault".

Maintenant, en fonction du compilateur, du loader cette zone peut varier. Donc un dépassement de 1 peut marcher si le heap est + grand que le tableau.

Mais si tu déclares une variable derrière ton tableau, elle sera aussi écrite dans le heap derrière ton tableau. En dépassant, tu peux donc ne pas planter, mais effacer d'autres variables ...

Ce sont les inconvénients d'un langage bas niveau comme le C, c'est toi qui gère la mémoire, à toi de savoir ce que tu fais.

D'autres langages, comme le java, gèrent la mémoire autrement, et tu n'as pas à t'en occuper.

Link to comment
Share on other sites

Un dépassement de 980 (test du prof avec 1000 en condition de sortie) en mémoire ça devrait effacer pas mal de trucs alors non? Enfn pour ce problème faudrait plutot voir directement avec les gourous qui codent les os.

Pour les autres langages, vu ce qu'on fait, du mapple ou mathematica ou autres du genre seraient certainement plus appropriés. Et pour le Java, meme sur les stations sun, ça doit etre assez lent :chinois::chinois: . Treve de plaisanterie, voici mon code tout beau tout neuf tout refait

#include<stdio.h>
#include<math.h>
#define TAILLE (20)

double		  tab[TAILLE];
double		  ref[TAILLE];
double		  exact[TAILLE];
double		  c[TAILLE];
double		  d[TAILLE];
int			 i,j;
FILE		   *f;

main()
{
	tab[0] = 1 / (M_E);
	exact[19] = 0;
	f = fopen("TD2_valeurs_exactes.dat", "r");
	if (f == NULL) {
			printf("ERREUR FICHIER f");
			return (-1);
	}
	for (i = 0; i < (TAILLE-1); i++) {
			tab[i + 1] = 1 - (i + 2) * tab[i];

	}
	for (i = (TAILLE-1); i > 0; i--) {
			exact[i - 1] = (1 - exact[i]) / (i+1);
	}
	for (i = 0; i <TAILLE; i++) {
	j=i+1;
	fscanf(f, "%lf", &ref[i]);
			printf("E[%d]=%e\n", j, tab[i]);
			printf("Par la 2ème méthode E[%d]=%e\n", j, exact[i]);
			printf("La valeur exacte est E[%d]=%e\n", j, ref[i]);
	c[i] = tab[i] - ref[i];
			d[i] = exact[i] - ref[i];
			if (ref[i] != tab[i]) {
					if (exact[i] != ref[i]) {
							printf("La 1ère méthode est erronée, la variation est de %e\nLa 2ème méthode est erronée, la variation est de %e\n\n", c[i], d[i]);
					} else {
							printf("La 1ère méthode est erronée, la variation est de %e\nLa 2ème méthode est correcte\n\n", c[i]);
					}
			} else {
					if (exact[i] != ref[i]) {
							printf("La 1ère méthode est correcte, la variation estde %e\nLa 2ème méthode est erronée, la variation est de %e\n\n", d[i]);
					} else {
							printf("La 1ère méthode est correcte, \nLa 2ème méthode est correcte\n\n");
					}
			}
	}
	fclose(f);
	return (0);
}

Cette fois y a plus de problème, enfin chez moi. Si quelqu'un peut me dire si y a un meilleur code ou des petits trucs à changer avant de prendre de mauvaises habitudes.

Link to comment
Share on other sites

Moui je vais permettre quelques remarques :yes:

En règle générale:

- Les variables globales, c'est mal ! Si possible évite d'en utiliser, pour plus d'infos voir le paragraphe "Règles de base" ici.

- Type ton main() (même si c'est vrai que c'est du "int" par défaut si je ne m'abuse), ce sera plus lisible.

- Comme type de retour de ton main(), plutôt que "return 0" ou "return -1", utilise les variables EXIT_FAILURE et EXIT_SUCCESS définies dans stdlib.h. En effet les codes d'erreur (ou de non erreur) diffèrent d'un système à l'autre. Par exemple sous Windows EXIT_SUCCESS vaut 1, et sous Unix 0.

Concernant ton code maintenant:

- Pourquoi c, d et ref sont des tableaux ? Tu peux utiliser de simples variables de type double. Elles seront réinitialisées à chaque passage dans la boucle mais ce n'est pas grave puisque apparemment tu ne t'en ressers pas. Autant gagner de la place mémoire !

- Quand tu testes la valeur de f à l'ouverture du fichier, pense à afficher avec fprintf(stderr,"erreur\n") . Ici ce n'est pas trop gênant mais parfois avec de simples printf les messages d'erreurs n'apparaissent pas. Pourquoi ? La fonction printf est équivalente à la fonction fprintf(stdout,"blabla"). Stdout désigne la sortie standard et est bufferisé (dès qu'il y a un \n dans le texte on affiche, ça optimise le programme donc). Stderr est non bufferisé et affiche directement le texte. Comme ça si tu as des erreurs de segmentation un jour dans tes programmes tu verras plus facilement d'où ça vient. Autant prendre les bonnes habitudes de suite.

J'espère que je n'ai pas dit trop de conneries :non:

Link to comment
Share on other sites

Merci pour les précisions mais doucement, j'apprends :transpi:

Pour les règles générales:

*les variables globales, c'était pour mieux voir, comme il n'y a qu'un seul gros bloc d'instruction ça revient au meme non? Mais autant prendre de bonnes habitudes maintenant. Un simple couper-coller dans le main devrait suffire.

*pour typer le main, ça sert à quoi exactement? On nous a balancé en exemple int main() sans raison. Et comme int c'est pour les entiers, je vois pas à quoi ça sert pour l'instant.

*pareil pour les types de retour, le prof nous a dit return (0) si tout va bien, et la vérification du fichier avec le return (-1) c'est lui qui l'a donnée telle quelle, et il nous a pas parlé des variables de stdlib.h. Parfois vaut mieux pas se poser trop de questions.

Pour mon code:

*c'est vrai que je peux définir c et d tout simplement en double (je l'avais pas vu), mais pour ref lorsque je scanne le fichier, j'ai juste un tableau à manipuler au lieu de 20 variables.

*pour fprintf, on nous a appris à l'utiliser sous la forme fprintf(f,"(texte_à_afficher)",(variable_de_stockage)); comme je l'ai dit plus haut le prof nous l'a donné tel quel.

Pour mon 3ème td en C, c'est pas trop mal j'ai pas d'erreur vraiment critique et en plus j'ai réussi tout seul.

Maintenant, j'ai plus qu'à supprimer les printf et utiliser fopen("nom_d_un_fichier","w") pour acquérir les données dans un fichier à part et m'en servir avec gnuplot.

Link to comment
Share on other sites

heu, juste une question stupide de noob.... pourquoi tu mets des parenthèses dans

#define TAILLE (20) ?

Y a pas besoin normalement, non?

Sinon à propos d'optimisation de code, si je ne m'abuse tu n'as pas besoin de mettre d'accolades lorsque qu'un test ou une boucle ne comporte qu'une instruction.

pour :

for (i = 0; i < (TAILLE-1); i++) {

tab[i + 1] = 1 - (i + 2) * tab;

}

tu peux mettre

for (i = 0; i < (TAILLE-1); i++)

tab[i + 1] = 1 - (i + 2) * tab;

Je sais que certains préfèrent en mettre quand même pour une question de meilleure lecture perso, mais bon :byebye:

Link to comment
Share on other sites

Non, dans #define on ne parenthèse que s'il y a plus d'un truc. ie:

#define m 70

ok pas de risque d'effet de bord

#define m2 m+5

pas ok : risque de bord

Ca permet d'éviter d'alourdir la syntaxe des define :)

(ceci dit, je ne nie pas que pour un débutant cela soit une bonne voire très bonne idée)

Link to comment
Share on other sites

->h0taru: comme l'ont dit kmlz et Baldurien, je parenthèse le define pour pas me faire couilloner avec les priorités de calcul lorsqu'il y a plusieurs termes. Si on fait #define taille a+b et plus loin taille/c ça revient à faire a+b/c alors qu'en général on veut plutot (a+b)/c. Lorsqu'il y a un terme c'est facultatif mais si on prend cette habitude, meme en étant très bon, lorsqu'il y a plusieurs termes il n'est pas impossible qu'on oublie la priorité des calculs=>tout faux le programme.

Pareil pour les accolades avec for: déjà c'est moins lisible (avis perso) car on voit pas le bloc d'instruction et lorsqu'il y a plusieurs instructions et qu'on oublie les accolades=>encore un programme tout faux.

C'est vraiment des pièges à andouille très facile à éviter alors tant qu'à faire autant ajouter 4 caractères pour pas se faire avoir.

->LePhasme: je pense avoir compris pourquoi on type le main, mais quelles sont les différentes commandes utilisables pour le faire? Ou alors int main() suffit dans (quasiment) tous les cas simples?

Link to comment
Share on other sites

emerge_problem> ensuite, savoir se relire est une autre chose :) avec un code propre (troll) pas de problèmes :)

Par contre, le coup des parenthèses sur les #define, à un seul terme, c'est plus si cool quand tu fais un gcc -E pour afficher le code source et corriger un bug de compilation...

Link to comment
Share on other sites

Je viens d'essayer le coup de gcc -E sur mon prog qui fait à peine 54 lignes en tout et je n'avais pas assez de place pour tout afficher dans mon terminal. Il vaut mieux regarder la ligne qui pose problème directement à partir de son éditeur de texte préféré après qu'une compilation ait loupé, en plus il y a la coloration syntaxique.

Pour le main, je sens que je le typer seulement en int, ça a pas l'air de poser de problème.

Link to comment
Share on other sites

normal.

Le type de retour par défaut de toute fonction, quand tu n'en mets pas, c'est int. Ca permet des codes crades entre autre.

Pour -E, faut voir à rediriger la sortie au bon endroit, et éventuellement à shunter les includes des librairies standards.

Si tu ne bosses pas avec des macros (genre #define list(type, nom) pour créer un squelette de liste générique), tu n'auras jamais de problèmes.

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...