代码似乎做了它应该做的所有事情,但最后它给出了一个分段错误。我是 C 的新手,所以我不确定这里发生了什么。
#include <stdio.h>
#include <stdlib.h>
const char *fileName = "data.txt";
int main(int argc, char** argv) {
FILE *file = fopen(fileName, "r");
int i, j;
int temp;
fscanf(file, "%d", &temp);
const int tickNum = temp;
fscanf(file, "%d", &temp);
const int pNum = temp;
struct data {
int process, tau, tick;
float alpha;
int ticks[tickNum];
};
struct data *p[pNum];
for(i = 0; i < pNum; i++) {
fscanf(file, "%d %d %f", &p[i]->process, &p[i]->tau, &p[i]->alpha);
for(j = 0; j < tickNum; j++) {
fscanf(file, "%d", &p[i]->ticks[j]);
}
}
fclose(file);
return (EXIT_SUCCESS);
}
从哪里开始?您正在处理大量问题。对于一个新的C程序员来说,有些并不是那么微不足道。我将讨论新程序员应该特别注意的问题,然后将它们合并到如何重构代码以解决这些问题的示例中。
让我们先来看看data
的声明。您面临的问题是您事先不知道ticknum
的价值。正如所有人在注释和其他答案中指出的那样,您不能在结构声明中使用ticks
元素数量的非常量声明。不允许使用可变长度数组(VLA)。问题是,如果编译器在末尾附加了一个可变长度的对象,编译器将不知道sizeof (struct data);
,从而无法对struct data
数组进行指针算术或数组索引
从 C99 开始,C 确实提供了灵活数组成员 (FAM),允许将最后一个成员声明为int ticks[]
-但是,如果它包含FAM
,则不能创建struct data
数组或在另一个结构或联合中包含struct data
。还有零长度数组 - 结构 hack,其中ticks
声明为int ticks[0];
它基本上用作 VLA 的标头,但也有类似的固有问题。
那么如何处理这种情况ticks
和ticknum
呢?您有两个选择。如果您知道ticknum
不能超过最大值,则可以为最大值声明一个常量(例如#define TICKNUM 32
),然后将ticks
声明为静态声明的数组int ticks[TICKNUM];
但是,对于所有少于TICKNUM
个刻度的p
元素来说,这是浪费的。如果您的struct data
数组中有大量元素,它也可能会耗尽您的堆栈空间。
选项二是将ticks
声明为指向 int 的指针(例如int *ticks;
),然后在p
数组的每个pnum
元素中分别动态分配ticks
struct data
。在这里,您可以准确调整从文件中读取ticknum
的内存使用量,并且由于您动态分配,因此内存是从堆中分配的,并且仅受可用内存的限制(由操作系统的内存管理器处理)。这是解决问题的正确方法,唯一的缺点是你有责任为每个ticks
数组分配,然后在完成每个阵列后释放每个阵列。
接下来,虽然样式由您决定,但 C 传统上避免使用camelCase
或MixedCase
变量名称,而支持所有小写,同时保留大写名称以用于宏和常量。(如果你想知道我为什么要改变你的pNum
和tickNum
名字......
验证、验证、验证所有输入(尤其是用户输入)、所有文件写入、所有内存分配以及文件写入后的所有文件关闭。如果您阅读它,请验证您使用的任何函数认为它读取了您期望读取的内容。所有函数都提供返回。使用它们来最低限度地验证所有输入、转换和内存分配。
除非您有理由在main()
内声明data
(这很好,但是...),否则通常您需要数据类型(例如struct data {....};
用文件范围声明的,因此您编写的任何函数等都具有可用的类型。标准 C 还规定在开始执行语句之前进行所有声明。实际上,标准希望在每个函数的开头声明所有变量(main()
是一个函数)。这并不总是可能的(或实际的),但要最大限度地坚持下去。
始终在启用编译器警告的情况下进行编译,至少-Wall -Wextra
(或编译器的等效警告),并且在编译时没有警告之前不要接受代码。(你可以从阅读、理解和解决编译器告诉你的所有问题中学到尽可能多的 C,就像从任何教程中学到一样)如果您已启用并解决了警告,您会发现data
的tick
元素在整个代码中未使用。
将所有这些部分放在一起,您可以重新排列代码以类似于以下内容。(我没有示例输入,所以我不得不稍微阅读一下茶叶)
#include <stdio.h>
#include <stdlib.h>
struct data {
int process, tau /*, tick */; /* tick unused in your code */
float alpha;
int *ticks;
};
int main (int argc, char** argv) {
const char *filename = argc > 1 ? argv[1] : "data.txt";
int ticknum = 0, pnum = 0;
struct data *p = NULL;
FILE *file = fopen(filename, "r");
if (!file) { /* validate file open for reading */
fprintf (stderr, "error: file open failed '%s'.n", filename);
return 1;
}
/* validate ALL input */
if (fscanf (file, "%d", &ticknum) != 1) {
fprintf (stderr, "error: read failure - ticknum.n");
return 1;
}
if (fscanf (file, "%d", &pnum) != 1) {
fprintf (stderr, "error: read failure - pnum.n");
return 1;
}
/* allocate and validate ALL memory allocations */
if (!(p = malloc (sizeof *p * pnum))) {
fprintf (stderr, "error: virtual memory exhausted.n");
return 1;
}
for (int i = 0; i < pnum; i++) {
if (fscanf (file, "%d %d %f", /* validate process, tau, alpha */
&p[i].process, &p[i].tau, &p[i].alpha) != 3) {
fprintf (stderr, "error: read failure process[%d].n", i);
return 1;
}
/* allocate/validate p[i].ticks */
if (!(p[i].ticks = malloc (sizeof *p->ticks * ticknum))) {
fprintf (stderr, "error: memory exhausted p[%d].ticks.n", i);
return 1;
}
for (int j = 0; j < ticknum; j++) { /* validate ticks[j] */
if (fscanf (file, "%d", &p[i].ticks[j]) != 1) {
fprintf (stderr, "error: read failure process[%d].ticks[%d].n",
i, j);
return 1;
}
}
}
fclose (file);
for (int i = 0; i < pnum; i++) { /* output data */
printf ("%2d %8d %8d %.3fn",
i, p[i].process, p[i].tau, p[i].alpha);
for (int j = 0; j < ticknum; j++)
printf (" ticks[%2d] : %dn", j, p[i].ticks[j]);
free (p[i].ticks); /* free p[i].ticks memory */
}
free (p); /* free allocated memory for p */
return 0;
}
示例输入文件
$ cat dat/ticks.dat
6 3
8152 1123 123.456
1 3 5 7 9 11
8153 2123 124.567
2 4 6 8 10 12
8154 3123 125.678
1 2 3 4 5 6
示例使用/输出
$ ./bin/ticks dat/ticks.dat
0 8152 1123 123.456
ticks[ 0] : 1
ticks[ 1] : 3
ticks[ 2] : 5
ticks[ 3] : 7
ticks[ 4] : 9
ticks[ 5] : 11
1 8153 2123 124.567
ticks[ 0] : 2
ticks[ 1] : 4
ticks[ 2] : 6
ticks[ 3] : 8
ticks[ 4] : 10
ticks[ 5] : 12
2 8154 3123 125.678
ticks[ 0] : 1
ticks[ 1] : 2
ticks[ 2] : 3
ticks[ 3] : 4
ticks[ 4] : 5
ticks[ 5] : 6
内存使用/错误检查
必须使用内存错误检查程序来确保不会尝试超出/超出分配的内存块的范围进行写入,不会尝试读取或基于未初始化值的条件跳转,最后确认已释放已分配的所有内存。
对于Linux来说,valgrind
是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。
$ valgrind ./bin/ticks dat/ticks.dat
==6270== Memcheck, a memory error detector
==6270== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==6270== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==6270== Command: ./bin/ticks dat/ticks.dat
==6270==
0 8152 1123 123.456
ticks[ 0] : 1
ticks[ 1] : 3
ticks[ 2] : 5
ticks[ 3] : 7
ticks[ 4] : 9
ticks[ 5] : 11
1 8153 2123 124.567
ticks[ 0] : 2
ticks[ 1] : 4
ticks[ 2] : 6
ticks[ 3] : 8
ticks[ 4] : 10
ticks[ 5] : 12
2 8154 3123 125.678
ticks[ 0] : 1
ticks[ 1] : 2
ticks[ 2] : 3
ticks[ 3] : 4
ticks[ 4] : 5
ticks[ 5] : 6
==6270==
==6270== HEAP SUMMARY:
==6270== in use at exit: 0 bytes in 0 blocks
==6270== total heap usage: 5 allocs, 5 frees, 696 bytes allocated
==6270==
==6270== All heap blocks were freed -- no leaks are possible
==6270==
==6270== For counts of detected and suppressed errors, rerun with: -v
始终确认已释放已分配的所有内存,并且没有内存错误。
仔细看看,如果您有任何其他问题,请告诉我。祝你的编码好运。
*p[pNum]
只是一个单独的指针字段,指向尚未存在的struct data
实例,就像你编写它的方式一样。它实际上尚未指向有效的内存区域,因此访问它会导致内存访问冲突。
摆脱多余的*
变成堆栈分配,或者使用malloc
在堆上分配实例。在第一种情况下,您还需要修改对scanf
的调用的语法,并习惯在同一术语中使用&
和->
时使用大括号,以避免对解析顺序感到困惑。
您实际上也可能打算编写(*p)[pNum]
,而这本来是指向结构字段的单个指针。
事实上,你还应该调高编译器的警告级别,因为它应该已经强烈抱怨了。将-Wall -Werror -Wextra -pedantic
添加到编译器命令行是一个良好的开端。
在这一行:
struct data *p[pNum];
声明一个数组,该数组将包含指向初始结构的指针, 但是最初没有初始化的结构,指针指向"无处",您应该创建结构并手动指向它们,如下所示:
for (i = 0; i < pNum, i++) {
p[i] = malloc(sizeof(struct data));
}
正如其他人所建议的那样,指针正在查看无效的内存区域,为结构变量分配内存以摆脱分段错误,我会建议如下。
struct data **p=malloc(pNum*sizeof *p);
for (i=0;i<pNum;i++){
p[i]=malloc(sizeof p);
}