代码似乎做了它应该做的所有事情,但最后它给出了一个分段错误。我是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);
}
答案 0 :(得分:4)
从哪里开始?您正在处理大量问题。对于新的C程序员来说,有些并不是那么微不足道。我将解决新程序员应特别注意的问题,然后将它们合并到一个示例中,说明如何重构代码以解决问题。
让我们首先宣读data
。您遇到的问题是您事先并不知道ticknum
的价值。正如所有人在评论和其他答案中都注意到的那样,你不能在结构声明中使用ticks
中元素数量的非常量声明。不允许使用可变长度数组(VLA)。问题是编译器不知道sizeof (struct data);
是否有一个可变长度的对象被添加到最后,这使得无法在struct data
从C99开始,C确实提供灵活阵列成员(FAM),允许最后一个成员的单个声明为int ticks[]
- 但如果包含struct data
,则无法在另一个结构或联合中创建struct data
或包含FAM
的数组。还有零长度数组 - struct hack ,其中ticks
被声明为int ticks[0];
,它基本上用作VLA的标题,但也有类似的固有问题。
那么如何使用ticks
和ticknum
处理这种情况?你有两个选择。如果您知道ticknum
不能超过最大值,则可以为最大值声明一个常量(例如#define TICKNUM 32
),然后将ticks
声明为静态声明的数组int ticks[TICKNUM];
但是,这对于p
小于TICKNUM
个刻度的所有元素都是浪费的。如果您的数组struct data
中有大量元素,它也会耗尽您的堆栈空间。
第二个选项是将ticks
声明为指向int 的指针(例如int *ticks;
),然后在每个{ticks
内分别动态分配pnum
您p
struct data
数组{1}}中的元素。在这里,您可以精确调整文件中ticknum
读取的内存使用大小,并且由于您动态分配,因此内存是从堆中分配的,并且仅受您可用内存的限制(由您的操作系统的内存管理器处理)。这是解决问题的正确方法,唯一的缺点是您需要为每个ticks
数组分配责任,然后在完成每个数组时释放它们。
接下来,虽然 style 取决于您,但C传统上避免使用camelCase
或MixedCase
变量名来支持所有小写同时保留大写名称以用于宏和常量。 (如果您想知道我为什么要更改您的pNum
和tickNum
名称...)
验证,验证,验证 所有输入(尤其是用户输入),所有文件写入,所有内存分配,以及文件写入后的所有文件关闭。如果您阅读它,请验证您使用的任何功能,认为它读取您希望它读取的内容。所有功能都提供返回。使用它们可以最低限度地验证所有输入和转换以及内存分配。
除非您有理由在data
内声明main()
(这很好,但是......),一般您希望您的数据类型(例如struct data {....};
声明为文件范围所以任何您编写的函数等具有可供使用的类型。标准C还规定所有声明都在您开始执行语句之前发生。实际上,标准要求在每个函数开头声明的所有变量(main()
都是函数这并不总是可行的(或实际的),但最大限度地坚持它。
始终使用编译器警告启用编译,至少-Wall -Wextra
(或编译器的等效项)和不接受代码,直到它编译时没有警告。 (您可以通过阅读,理解和解决编译器告诉您的所有问题,从任何教程中学到很多C)如果您启用并解决了警告,那么您可能会发现tick
data
的元素在整个代码中未使用。
将所有这些部分组合在一起,您可以重新安排代码以使其工作类似于以下内容。 (我没有示例输入,所以我不得不读一下茶叶)
#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 %.3f\n",
i, p[i].process, p[i].tau, p[i].alpha);
for (int j = 0; j < ticknum; j++)
printf (" ticks[%2d] : %d\n", 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
始终确认已释放已分配的所有内存并且没有内存错误。
仔细看看,如果您有任何其他问题,请告诉我。祝你的编码好运。
答案 1 :(得分:1)
*p[pNum]
只是一个指向struct data
现有实例的单独指针的字段,就像你编写它一样。它实际上并没有指向有效的内存区域,因此访问它会导致内存访问冲突。
取消多余的*
代替堆栈分配,或者使用malloc
在堆上分配实例。在第一种情况下,您还需要修改对scanf
的调用语法,并习惯在同一术语中使用&
和->
时使用大括号,以避免让你自己解决它的解决顺序。
您实际上也可能打算编写(*p)[pNum]
,而这将是指向结构字段的单个指针。
事实上,你也应该提高你的编译器的警告级别,因为它应该已经暴力抱怨了。将-Wall -Werror -Wextra -pedantic
添加到编译器命令行是一个良好的开端。
答案 2 :(得分:1)
在这一行:
struct data *p[pNum];
您声明一个包含指向初始化结构的指针的数组, 但最初没有初始化的结构,指针指向“无处”,你应该手动创建结构和指针,就像这样:
for (i = 0; i < pNum, i++) {
p[i] = malloc(sizeof(struct data));
}
答案 3 :(得分:-1)
正如其他人所建议的那样,指针正在查看无效的内存区域,为struct变量分配内存以摆脱Segmentation Fault,我建议如下所示。
struct data **p=malloc(pNum*sizeof *p);
for (i=0;i<pNum;i++){
p[i]=malloc(sizeof p);
}