Премини към съдържанието

Архивирана тема

Темата е твърде стара и е архивирана. Не можете да добавяте нови отговори в нея, но винаги можете да публикувате нова тема, в която да продължи дискусията. Регистрирайте се или влезте във вашия профил за да публикувате нова тема.

Mariya_I

Динамично заделяне на памет в С

Препоръчан отговор


Здравейте! Нова съм в програмирането и се опитвам да напиша програма, която чете от файл, извежда съдържанието на екрана и след това трябва да премести прочетените елементи в динамична таблица, което не ми е много ясно. Отделно ми дава грешка, когато се опитвам да запиша файла във функцията Create. Знам че е много, но ще се радвам на малко помощ. :)

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <memory.h>#define N 5typedef struct {    char name[10];    int num;    float mark;}STUD;void myMenu();void Create();void Print();void Add();void Move();int end=0;main(){    myMenu();    return 0;}void myMenu(){    int choice;do{    printf("n1.Create the file.n2.Print the file.n3.Moven");    printf("Your choice: ");    scanf("%d",&choice);    switch(choice){    case 1: Create(); break;    case 2: Print(); break;    case 3: Move(); break;    }}while(choice!=4);}void Create(){    FILE *fp;    int N,i;    STUD a;    char filename[15];    puts("Enter file name:");    fflush(stdin);    gets(filename);    if((fp=fopen("filename","w+"))==NULL){    printf("Cannot open file.n");    exit(1);    }    printf("Enter number of students: ");    fflush(stdin);    scanf("%d",&N);        for(i=0;i<N;i++){        printf("Enter name:n ");        scanf("%s",&a.name);        printf("nEnter number: ");        scanf("%d",&a.name);        printf("nEnter average mark: ");        scanf("%f",&a.mark);        end=N;    }  for(i=0; i<N; i++){    fwrite(&a, sizeof(STUD), 1, fp);  }fclose(fp);}void Print(){    FILE *fp;    STUD *a;    if((fp=fopen("filename","r"))==NULL){    printf("Cannot open file.n");    exit(1);    };    while(fread(&a, sizeof(STUD),1,fp)==1){    printf("Name %s, Number %d, Average Mark %f",a->name, a->num, a->mark);    }    fclose(fp); }void Move(){    FILE *fp;    STUD *array, *a;    int i;    char word[4];    if((fp=fopen("filename","a+"))==NULL){        printf("Cannot open file.n");        exit(1);    }    array=(STUD *)malloc(N*sizeof(STUD));    if(array==NULL)        exit(1);    memcpy(&array[*i], &a, sizeof(STUD));    (*i)++;    printf("Next student: ?, yes/no: ");    fflush(stdin);    if(strcmp(gets(word),"yes")==0){        a=(STUD *)malloc(sizeof(STUD));        Add(&a);  }  if(fwrite(&array, sizeof(STUD), 1, fp)==NULL){    printf("Error writing file.n");    exit(1);  }  fclose(fp);}void Add(){    STUD a;    printf("Enter name: ");    scanf("%s",&a.name);    printf("Enter number: ");    scanf("%d",&a.num);    printf("Enter average mark: ");    scanf("%f",&a.mark);}

Сподели този отговор


Линк към този отговор
Сподели в други сайтове

Не съм пускал програмата, защото само от първоначалния преглед на функцията Create ми се набиха солидно количество грешки. Прегледайте си изцяло кода, ако искате да научите нещо, а не да отбиете номера. Посочвам грешките, докъдето стигнах, преди да се откажа.

  • fflush(stdin); по стандарт води до неопределено поведение и не трябва да се ползва. По принцип изчистването на стандартния вход в C не е съвсем тривиално, по-добре го пропуснете засега.
  • искате име на потребител в променливата filename, но после когато отваряте файл използвате низа "filename". Това ще създаде файл наречен filename, а не с въведеното име. Махнете кавичките.
  • Когато въвеждате данни за номер използвате полето от структура за име: a.name вместо.a.num. Между другото 10 символа са много малко за име.
  • След въвеждането на броя студенти, първо въвеждате N пъти данните в една и съща структура, всеки път върху предните, и после N пъти записвате последните въведени данни във файла. Поставете четенето от клавиатурата и записа във файл в един и същ цикъл, а не в два поредни.
  • във функцията Move: същата грешка при отваряне на файла - трябва да измислите друг начин за съхранение на името защото тук дори и да махнете кавичките, нямате променлива наречена filename - тази въведена преди е само за  функцията Create. Отделно нищо не пречи на потребителя да извика директно Move без Create. И последно, отваряте файла за добавяне, а вероятно се очаква да четете от него.
  • memcpy се опитва да копира от a в array. Обаче указателят a не е инициализиран изобщо. Това е много груба грешка и ще доведе директно до краш... Отделно и i не е инициализирано. Съвсем отделно i дори не е указател че да ползвате оператора *...

До тук издържах. Оправете тези неща, изяснете какво и как искате да направите и ще продължим...

Сподели този отговор


Линк към този отговор
Сподели в други сайтове

Не съм пускал програмата, защото само от първоначалния преглед на функцията Create ми се набиха солидно количество грешки. Прегледайте си изцяло кода, ако искате да научите нещо, а не да отбиете номера. Посочвам грешките, докъдето стигнах, преди да се откажа.

  • fflush(stdin); по стандарт води до неопределено поведение и не трябва да се ползва. По принцип изчистването на стандартния вход в C не е съвсем тривиално, по-добре го пропуснете засега.
  • искате име на потребител в променливата filename, но после когато отваряте файл използвате низа "filename". Това ще създаде файл наречен filename, а не с въведеното име. Махнете кавичките.
  • Когато въвеждате данни за номер използвате полето от структура за име: a.name вместо.a.num. Между другото 10 символа са много малко за име.
  • След въвеждането на броя студенти, първо въвеждате N пъти данните в една и съща структура, всеки път върху предните, и после N пъти записвате последните въведени данни във файла. Поставете четенето от клавиатурата и записа във файл в един и същ цикъл, а не в два поредни.
  • във функцията Move: същата грешка при отваряне на файла - трябва да измислите друг начин за съхранение на името защото тук дори и да махнете кавичките, нямате променлива наречена filename - тази въведена преди е само за  функцията Create. Отделно нищо не пречи на потребителя да извика директно Move без Create. И последно, отваряте файла за добавяне, а вероятно се очаква да четете от него.
  • memcpy се опитва да копира от a в array. Обаче указателят a не е инициализиран изобщо. Това е много груба грешка и ще доведе директно до краш... Отделно и i не е инициализирано. Съвсем отделно i дори не е указател че да ползвате оператора *...

До тук издържах. Оправете тези неща, изяснете какво и как искате да направите и ще продължим...

@Flare: Колега, ако нямате нищо против, ще допълня точките Ви, с това, което открих от момента в който силите Ви не са издържали. Ако имате нещо против, драснете едно ЛС да махна поста си :).

 

- В метода Move() извикваме метод Add по следния начин Add(&a). По - надолу обаче виждаме, че метода не е приема такава стойност за параметър -> Add().

- В самия метод Add() логиката поне на мен ми е странна - създаваме локална променлива, попълваме я през входа на конзолата и ... излизаме ... Или трябва да я върнем тая променлива а, или трябва метода да приема параметър указател (поне по начина, който се вика по-горе, това имам чувството, че трябва да прави) и да се махне локалната променлива а, съществуваща в момента.

- Като още една забележка, мисля, че е добра практика всички потоци, що имаме да се затварят - независимо дали са записали данни във файл или не. С това наум, трябва да се прегледат всички изходни точки на програмата (exit/return) и да се изпозатворят потоците. Възможно е exit да освобождава все ресурси що има, но аз лично бих се застраховал.

 

Това е което успях да видя. Дано Ви е било от помощ.

 

Поздрави !

Сподели този отговор


Линк към този отговор
Сподели в други сайтове

@Flare: Колега, ако нямате нищо против, ще допълня точките Ви, с това, което открих от момента в който силите Ви не са издържали. Ако имате нещо против, драснете едно ЛС да махна поста си :).

Даже благодаря за допълнението :D Пази Боже да имам нещо напротив. Не мога да си представя, какво право и основание имам да казвам, какво да пишете, особено, като е вярно. А да си триете поста, щото не харесал някому при положение, че не е нарушил никакво правило - брррр... :P

Сподели този отговор


Линк към този отговор
Сподели в други сайтове

Аз съм била по-зле отколкото си мислех...изпотих се докато четях отоговорите. Hо много благодаря, бяхте много полезни. Сега сядам да се трудя :)


Сподели този отговор


Линк към този отговор
Сподели в други сайтове

Значи... оправих някой от грешките, но отново забих. Това което искам да направя във функцията Move()  (да прочета файла и да преместя прочетените елементи в динамична таблица) не ми е ясно как да го направя...

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <memory.h>typedef struct {    char name[40];    int num;    float mark;}STUD;void myMenu();void Create();void Print();void Add(STUD *a);void Move();int end=0;main(){    myMenu();    return 0;}void myMenu(){    int choice;do{    printf("n1.Create the file.n2.Print the file.n3.Moven");    printf("Your choice: ");    scanf("%d",&choice);    switch(choice){    case 1: Create(); break;    case 2: Print(); break;    case 3: Move(); break;    }}while(choice!=4);}void Create(){    FILE *fp;    int N,i;    STUD a;    if((fp=fopen("students","w+"))==NULL){    printf("Cannot open file.n");    exit(1);    }    printf("Enter number of students: ");    scanf("%d",&N);        for(i=0;i<N;i++){        printf("Enter name:n ");        scanf("%s",&a.name);        printf("nEnter number: ");        scanf("%d",&a.num);        printf("nEnter average mark: ");        scanf("%f",&a.mark);        end=N;        if(fwrite(&a, sizeof(STUD), 1, fp)!=1){        printf("Error writing data.n");        exit(1);    } }fclose(fp);}void Print(){    FILE *fp;    STUD *a;    if((fp=fopen("students","r"))==NULL){    printf("Cannot open file.n");    exit(1);    };    while(fread(&a, sizeof(STUD),1,fp)==1){    printf("Name %s, Number %d, Average Mark %f",a->name, a->num, a->mark);    }    fclose(fp); }void Move(){    FILE *fp;    STUD *array, *a;    int *i,N;    char word[4];    *i=0;     N=0;    if((fp=fopen("students","r"))==NULL){        printf("Cannot open file.n");        exit(1);     }    array=(STUD *)malloc(N*sizeof(STUD));    if(array==NULL)        exit(1);    memcpy(&array[*i], &a, sizeof(STUD));    (*i)++;    printf("Next student: ?, yes/no: ");    if(strcmp(gets(word),"yes")==0){        a=(STUD *)malloc(sizeof(STUD));        Add(a);  }  if(fwrite(&array, sizeof(STUD), 1, fp)!=1){    printf("Error writing file.n");    exit(1);  }  fclose(fp);}void Add(STUD *a){    printf("Enter name: ");    scanf("%s",&a->name);    printf("Enter number: ");    scanf("%d",&a->num);    printf("Enter average mark: ");    scanf("%f",&a->mark);}

Сподели този отговор


Линк към този отговор
Сподели в други сайтове

Така, от това което виждам, сега написано и при положение предните ми коментари е, че не ви е ясно, какво е указател и за какво се ползва. Аз тук ще направя някои обяснения, но ви умолявам да прочетете пак  - темата е ужасно обширна и два пъти по-ужасно важна.

Първо. Указателите се използват за точно определени неща. Ако не знаете за какво и как да ги ползвате - не го правете. В случая i. Бихте ли казали за какво е необходимо тази променлива да е указател? Това не е заяждане - просто когато програмирате, всяко нещо трябва да е в кода защото искате да постигнете нещо с него и аз се опитвам да разбера какво сте имали предвид тук.

Указателят е променлива, която съхранява адрес в паметта. В C освен това указателите могат и да "носят" типа на данните, които се намират на този адрес. Тогава казваме, че променливата е указател към тип еди-какъв си. И тук идва един факт, осъзнаването на който, е сериозен проблем дори и за не толкова нови в езика: работим ли с указател, трябва да разграничаваме 2 различни стойности - тази на самия указател, която представлява адреса на променливата към която сочи и стойността на самата тази променлива - която ни интересува. Тоест трябва да се уверим, че и двете са валидни преди да ги ползваме.

Ето и първата грешка в функцията. Указателят i (първата от гореспоменатите стойности) не е инициализиран - т.е. сочи към произволен адрес в паметта. И с инструкцията:

*i = 0;

ще се опитате да запишете 0 на този произволен адрес. В общият случай процесорът ще разбере, че пипате, където не ви е работа и ще каже на операционната система. Това е фатална грешка и операционната система директно ще ви убие програмата. Разкарайте тия звездички от дефиницията и употребата на променливата - това не е необходимо да е указател.

Променливата array обаче си я инициализирате правилно. Има два начина за инициализиране на указател: като заделите памет (който е ползван тук) или като го насочите към съществуваща вече променлива. Към момента след извикването на malloc и съответната проверка указателят array сочи към памет, която си е само ваша и е голяма достатъчно да побере N променливи от тип STUD - това е вашата "динамична таблица".

Сега относно операторът &. Очевидно идея си нямате какво прави, защото в 7 от 12 общо употреби сте го ползвали грешно. Той връща адреса на дадена променлива (стойността на указател към нея).

memcpy-то - копирате от а в array. Можете ли да ни кажете какво има в a, че да го копирате? Това ви го посочих още предния път.

След това идва голямо объркване - нали уж ще четем от файл в динамичната таблица? защо тогава използвате функцията със странното име add която чете от клавиатурата и след това се опитвате да запишете стойност от array, който не съдържа нищо смислено (нали току що сте си заделили тази памет - не сте я попълвали) във файл, който е отворен за четене. Ъ:ohmy:  По мое мнение трябва да направите цикъл (или направо едно "голямо" четене на всички елементи - ако ги знаете колко са от файла в таблицата array - така например както във функцията Print. Променливата a изобщо не ви трябва както и заделянето на памет за нея...

Сподели този отговор


Линк към този отговор
Сподели в други сайтове

Привет, няколко забележки от мен при поглеждането на кода:

 

Във функция Move() най-вероятно програмата ще се счупи, тъй като в локалните променливи заявявате указател i, а след това, без да му е присвоен адрес, записвате на този адрес нула. Локалните променливи не се инициализират и първоначално може да имат всякакви стойности, което значи, че с оператора * се обръщате към неизвестен случаен адрес от паметта, сочен от указателя i - нещо, което никога не трябва да се прави. Този адрес може да е в сегмента на програмата, при което е възможно да повредите вече записани данни в паметта и после да се чудите на странни резултати, но далеч по-вероятно е да сочи някъде "навън", при което програмата ще се "счупи".

 

И още няколко стилови отметки:

 

Функцията main() е входната точка на програмата, а след изпълнение следва да се върне стойност на операционната система (обикновено 0 при успешно изпълнение и различно от 0 при друго). Затова, съгласно C стандарта, main() е от тип int, като с return /може и exit()/ се връща съответния код/резултат от изпълнението. При деклариране на функцията без тип, по подразбиране се приема int, а при пропускане на return - че връща 0, но като добра практика е желателно да изписвате типа. Освен това, след C99 (стандарта на езика от 1999 г.), компилаторът ще пусне предупреждение при липса на едно от посочените.

 

Също така, празните скоби '()' значат функция, която не приема параметри, само в първоначалната версия на езика С (K&R C). В последващи стандарти (C89 и нататък) празните скоби описват функция, която може да приема всякакъв брой всякакви параметри. За да покажете изрично, че функцията не приема никакви параметри (при което и компилаторът ще изписука при опит за извикване с параметри), то посочвайте в скобите '(void)'.

 

П.П.: Видях, че flare е отговорил малко преди мен, както и за оператора за "извличане" на адрес &. Извинявам се за дублирането.

П.П.П.: В бързането си съм объркал имената на указателите - коментирал съм за а, имайки предвид i !

Сподели този отговор


Линк към този отговор
Сподели в други сайтове

×

Информация

Поставихме бисквитки на устройството ви за най-добро потребителско изживяване. Можете да промените настройките си за бисквитки, или в противен случай приемаме, че сте съгласни с нашите условия за ползване.