C: смешение указателей
#include<stdio.h>
#include<stdlib.h>
typedef struct{
char cStdName[50];
int nStdNum;
char cStdClass[4];
float dStdAvg;
}student;
student* students;
int cmp(const void* a, const void* b);
void main() {
int num = 0,i=0;
FILE *f;
printf("Number of students:");
scanf("%d", &num);
students = (student*)malloc(num*sizeof(student));
for(i=0;i<num;++i){
student* ptr = students+i*sizeof(student);
printf("Name:");
scanf("%s", ptr->cStdName);
printf("Num");
scanf("%d", &ptr->nStdNum);
printf("Class:");
scanf("%s", ptr->cStdClass);
printf("Grade:");
scanf("%f", &ptr->dStdAvg);
}
f = fopen("bin.bin","wb");
fwrite(&num,sizeof(int),1,f);
fwrite(students,sizeof(student),num,f);
fclose(f);
system("pause");
}
Предполагается, что в бинарном файле будет выведено количество учеников и весь массив структур, и он будет работать с 1 учеником. Но когда я добавляю>=2 человек, файл выглядит так: http://i.imgur.com/LgL8fUa.png
Если я добавлю только одного студента, все еще есть некоторая чепуха в пути Windows: http://i.imgur.com/s7fm9Uv.png, все в порядке, программа, которая читает файл, игнорирует все после NULL(я имею в виду, для первого массива символов).
Я думаю, что проблема где-то в цикле for() и манипулировании указателем, но я не могу сказать, где.
4 ответа
student* ptr = students + i * sizeof(student);
В C арифметика указателей уже включает sizeof(student)
, Вы будете читать после конца вашего массива.
student* ptr = students + i;
Тем не менее, вы заметите доступ к ptr
это то же самое, что доступ к students[i]
,
Есть несколько проблем с вашим кодом:
- Прежде всего, как сказал Кириленко, вы должны использовать
students[i]
в вашем коде. В твоем случае,students + i * sizeof(student)
выходит за пределы. - Это никогда не хорошая идея, чтобы использовать
fwrite
с массивом структур. Это потому, что компилятор может добавить некоторое пространство между членами структуры (заполнение), что означает, что когда вы передаете массив структур вfwrite
, байты заполнения (которые будут содержать мусор) будут напечатаны. - То же самое относится и к членам массива char в вашей структуре. Все неиспользуемые байты будут содержать мусор, который будет напечатан при использовании
fwrite
, Лучше использоватьstrlen
определить, сколько символов чтения содержит каждый массив символов.
Вот как я могу записать массив студентов в файл:
void write(students* array, int len, FILE* out)
{
int i;
fwrite(len, 1, sizeof(len), out);
for (i = 0; i < len; i++){
fwrite(array[i]->cStdName, 1, strlen(array[i]->cStdName), out);
fwrite(array[i]->nStdNum, 1, sizeof(array[i]->nStdNum), out);
fwrite(array[i]->cStdClass, 1, strlen(array[i]->cStdClass), out);
fwrite(array[i]->dStdAvg, 1, sizeof(array[i]->dStdAvg), out);
}
}
Ответ Кириленко должен решить вашу непосредственную проблему, но есть ошибки от тривиальных до серьезных почти в каждой строке этой программы. В зависимости от того, какова ваша более крупная цель, вам, возможно, не потребуется исправлять их все, но я все равно запишу их все, чтобы проиллюстрировать размер айсберга.
void main() {
int main(void)
, int
это абсолютное требование. На C (не C++) написание ()
для списка аргументов функции означает, что аргументы не определены, а не то, что аргументов нет; технически вы можете избежать неприятностей здесь, потому что это определение функции, но это плохой стиль.
Открывающая фигурная скобка определения функции всегда идет на одной строке, даже если все остальные открывающие скобки обнимаются.
int num = 0,i=0;
Непоследовательный интервал. Инициализации не нужны.
printf("Number of students:");
Некоторые руководства по стилю предпочитают fputs("Number of students", stdout);
когда вы не используете printf
Возможности форматирования. Но некоторые компиляторы могут сделать преобразование для вас, и это не имеет большого значения.
scanf("%d", &num);
Никогда не используйте scanf
, fscanf
, или же sscanf
, так как:
- Числовое переполнение вызывает неопределенное поведение. Среде выполнения C разрешено сбой вашей программы только потому, что кто-то набрал слишком много цифр.
- Некоторые спецификаторы формата (особенно
%s
, который вы используете позже в этой программе!) небезопасны точно так жеgets
это небезопасно, то есть они будут весело писать за концом предоставленного буфера и приводить к аварийному завершению работы вашей программы (эта конкретная программа не выглядит для меня чувствительной к безопасности, но всегда следует кодировать так, как будто ваши программы по крайней мере несколько опасны таким образом), - Они чрезвычайно затрудняют правильную обработку искаженного ввода.
Правильный способ прочитать одно неотрицательное число от пользователя так:
unsigned long getul(void)
{
char buf[80], *endp;
unsigned long val;
for (;;) {
fgets(buf, 80, stdin);
val = strtoul(buf, &endp, 10);
if (endp != buf && *endp == '\n')
return val;
if (buf[strlen(buf)] != '\n')
while (getchar() != '\n')
/* discard */;
fprintf(stderr, "*** Enter one nonnegative integer, smaller than %lu.\n",
ULONG_MAX);
}
}
Здесь вы можете обойтись без буфера фиксированного размера, потому что ничейный ULONG_MAX не настолько велик, что не умещается в 80 символов.
students = (student*)malloc(num*sizeof(student));
Вы должны использовать calloc
здесь, чтобы при записи ваших структур на диск они не были полны мусора. (Или напишите пользовательские сериализаторы, как предложено Александросом, это также поможет избежать проблемы.)
for(i=0;i<num;++i){
Предпочитаемый стиль for (i = 0; i < num; i++) {
, В дополнение к расстоянию, используйте i++
вместо ++i
чтобы i
появляется в одной и той же позиции во всех трех выражениях; это облегчает чтение.
student* ptr = students+i*sizeof(student);
student* ptr = students + i;
как обсуждалось в другом месте.
printf("Name:");
scanf("%s", ptr->cStdName);
Смотрите комментарии выше. Вы хотите другую вспомогательную функцию, например так:
void getstr(char *buf, size_t len)
{
size_t n;
for (;;) {
fgets(buf, len, stdin);
n = strlen(buf);
if (n < len && buf[n] == '\n') {
memset(buf+n, 0, len-n);
return;
}
while (getchar() != '\n')
/* discard */;
fprintf(stderr, "*** Enter no more than %lu characters.",
(unsigned long)(len-1));
}
}
...
scanf("%f", &ptr->dStdAvg);
И здесь вам нужна еще одна вспомогательная функция. getf
точно так же, как getul
кроме того, что использует strtod
и, конечно, сообщение об ошибке немного отличается.
f = fopen("bin.bin","wb");
Разве это не очень общее имя файла? Вероятно, у пользователя должен быть способ указать это.
fwrite(&num,sizeof(int),1,f);
Вашему формату файла нужен магический номер.
fwrite(students,sizeof(student),num,f);
Вы записываете двоичные данные на диск в порядке байтов процессора. Это может не быть проблемой для этого приложения, но имейте в виду, что в будущем у вас может возникнуть головная боль кросс-платформенной совместимости. (Лично для того, что выглядит так, как будто вы делаете, я бы использовал текстовую сериализацию, такую как JSON, или простую базу данных без демонов, такую как sqlite.) См. Ответ Александроса для более потенциальных проблем с этим форматом файла.
Это редко проблема при записи в файлы на диске, и это не та программа, чей вывод куда-то передается по каналу, но я все же упомяну, что fwrite
не гарантирует запись всех данных, которые вы предоставляете. Технически вам нужно позвонить fwrite
в цикле, как это:
size_t r, n = sizeof(student) * num;
char *p = (char *)students;
while (n > 0) {
r = fwrite(p, 1, n, f);
if (r == 0) break; /* write error */
n -= r;
p += r;
}
Чтобы это работало правильно, вы должны сделать умножение самостоятельно и передать 1 для второго аргумента fwrite
; в противном случае короткая запись может закончиться в середине "элемента данных", и у вас нет возможности узнать, что это произошло.
fclose(f);
Проверьте ошибки записи перед закрытием файла.
if (ferror(f) || fclose(f)) {
perror("bin.bin");
return 1; /* unsuccessful exit */
}
...
system("pause");
Просто return 0
, Программы, которые заставляют вас нажимать клавишу для выхода, являются пакетно-недружественными.
Назначение указателя может быть за пределами цикла один раз, и вы можете увеличивать указатель в конце цикла for. Попробуй это.
Хорошо. Вот попытка объяснить, что происходит: ptr указывает на первый элемент в списке студентов структурных типов, и когда вы увеличиваете ptr в конце цикла for, он указывает на следующего студента в списке.
---
students = (student*)malloc(num*sizeof(student));
student* ptr = students;
for(i=0;i<num;++i){
printf("Name:");
----
----
ptr++;
}