【问题标题】:End condition for a factorising loop分解循环的结束条件
【发布时间】:2012-06-07 18:01:11
【问题描述】:

晚上好,

我在分配作业时遇到问题。

基本上,我们需要编写一个程序来计算给定stdin 的质因数。数据只能通过stdin 进入程序,无论是echo 还是< file.txt。数据流永远不会超过 80 个字符(它们可以是数字,也可以不是数字)。

我在程序中使用的函数是read()strotol()strtok(),“无关”的代码流程如下:

  1. 使用malloc 分配80 个初始字节的内存。
  2. 将读取的字符数存储在intread()(我相信是最后一个\0)。
  3. realloc() 重新分配内存以节省尽可能多的内存(我知道在这种情况下这很简单,但是哦……)。

现在是棘手的一点:

  1. 由于数据必须用空格隔开,所以要检查的最大项目数为:(n/2)+1,其中n是在上点nº2读取的字符数。
  2. 创建一个long 数组,其最大大小为在第 1 点获得的数字。
  3. strtol(strtok(line, delim), &end, 10)的结果填充numbers[0]
  4. 1 添加到counter 并进入while 循环:

    while((numbers[counter] = strtol(strtok(NULL, delim), &end, 10)) != NULL) {
        if(!*end) {
            // Check whether it's 0, 1, negative, prime or extract its factors
        }
        else {
            fprintf(stderr, "\"%s\" is not a non-negative integer", end)
        }
        counter++;
    }
    

现在,这里有一些输入和它们的输出:

输入:echo 1 2 3 4 5 6 7 8 9 10 | ./factors

输出:

1
2    
3
    2    2
5
    2    3
7 
    2    2    2
    3    3
    2    5
Segmentation Fault (core dumped). 

输入./factors < integers.txt 其中 integers 包含一列整数。

输出:

所有的整数都被分解得很好,最后打印出一个:

Segmentation Fault (core dumped). 

输入:echo abc 12 13 14 15 | ./factors

输出:

"abc" is not a non-negative integer
13
    2    7
    3    5
Segmentation Fault (core dumped). 

输入:echo -1 -2 -3 -4 -5 | ./factors

输出:

"-1" is not a non-negative integer
"-2" is not a non-negative integer
"-3" is not a non-negative integer
"-4" is not a non-negative integer
"-5" is not a non-negative integer

输入:echo abc abc abc | ./factors

输出:

"abc" is not a non-negative integer

(并且不继续检查)。

输入:echo 3 4 0 6 7 | ./factors

输出:

3
    2    2

(并且不继续检查)。

据我所知,当遇到0、多个非integer 实例或基本上在基于integer 的健康数据流的末尾时,它会失败。

知道我该如何解决这个问题,为什么它会如此明显地随机失败?

我应该让你知道我是 C 新手...

非常感谢您。

================================================ =====

EDIT1:根据要求,这里是生成numbers[]的代码片段,并从stdin读取:

char *line;
char *end;
char *delim = " \n";
int charsread, counter; 

line = malloc(80);
charsread = read(0, line, 81);
if (charsread == 0) {
    return EX_OK;
}
realloc(line, charsread);
maxelem = (charsread / 2) + 1;
long numbers[maxelem];
numbers[0] = strtol(strtok(line, delim), &end, 10);
if (!*end) {
    zeroone(numbers[0]);
}
else {
    fprintf(stderr, "\"%s\" is not a non-negative integer\n", end);
}
counter = 1;
while [...]

【问题讨论】:

  • Input: echo abc abc abc | ./factors,strtol发生错误(并转换结果== 0)返回0,while(... != NULL)break while循环。
  • "abc" is not a non-negative number13 之间的第二个示例中是否也缺少一行?这是一个实际错误,还是仅仅来自您的重新转录?
  • 另外,zeroone 是做什么的???

标签: c strtok factors strtol


【解决方案1】:

尝试使用 gdb 调试段错误,通过设置适当的环境变量使其在段错误时转储内核,或直接在 gdb 中运行。 Segfault 意味着您正在读取/写入您不应该读取/写入的一部分内存。随机性意味着您可能正在破坏堆栈或其他东西。我认为“printf”是罪魁祸首,检查他们的论点。您也没有检查数字是否小于数组长度?它可能会超出它吗?

【讨论】:

  • 另外我建议你把这些东西分成很好的小函数,然后编写单元测试看看它们是否按照你的想法做。
  • 单元测试对于 OP 来说可能有点不直观,因为它是一项家庭作业。不一定,但很有可能。
  • 当然可以,但是您可能不太清楚您的意思,并且搜索以了解有关单元测试的更多信息对于新手来说通常是相当可怕的,因为您最终会得到一堆复杂的框架。但总的来说,我支持你。
  • 首先感谢您的回答。 @MarkusMikkolainen 如果通过单元测试您的意思是拆分为功能,我已经在检查 1/0、负数、素数和因子提取的区域中这样做了。您认为在 while 循环中拆分句子是否有用?
  • @MarkusMikkolainen & @haylem :顺便说一下,我不完全确定if (!*end) 位,因为我是从本网站上的另一个问题中得到的。你认为可能是这样吗? printfs 怎么能对代码的无意行为负责呢?检查数字是否小于数组长度是什么意思?看到输出,我可以看出它正在选择(当它选择它们时)流中的每个数字。
【解决方案2】:

好的,让我们在现场检查一些事情,看看我们是否可以解决您的问题。


这在您的程序中不是必需的:

realloc(line, charsread);

尝试减少内存占用是正确的,但是如果分配太多,那又如何呢?它是 80 个字节。不要把它复杂化,因为你的盘子里已经有足够的了。


这很奇怪:

maxelem = (charsread / 2) + 1;
long numbers[maxelem];

这很好,适用于您的情况,但您可以通过计算数字组来更准确地确定元素的数量。


我建议尝试在 lopp 中进行整个提取


关于实际的segmentation fault...我可能错了,因为我没有机会在我的机器上重现它,但我认为错误的发生是因为你没有用@987654325 终止你的line @ 特点。在 C 语言中,我相信您已经通过您发布的 sn-ps 的外观知道,行通常是我们所说的“NULL 终止”,所以按照惯例我们用值 0 来终止它们,这是数值对于NUL 字符,也表示为\0(这有时有点令人困惑,因为那时人们会混淆“NULL 终止”、“NUL”字符和“NULL 指针”,它们是不同的东西)。

这会使您的程序中断,因为最终您的程序会尝试使用strtok 读取行尾,但它不知道在哪里停止。它对缓冲区长度和在此缓冲区中停止的位置没有良心。所以它继续读取,到达一个不允许访问的内存地址,因此出现segmentation fault

所以你只想:

/*
** If you keep your realloc, you need to allocate for the number of read
** characters from stdin, and for an extra char to terminate.
*/
line = realloc(line, charsread + 1); 
/*
** Terminate the string.
*/
line[charsread] = '\0';

更新:啊,你实际上就快到了,你有逻辑,但可能只是错过了这一点......你甚至自己写了这个:

通过 read() 将读取的字符数存储在 int 中(我相信,最后一个 \0)。

这部分是正确的。如果您真的得到了一行 80 个字符,那么您的 read 调用将在最后返回带有 \0 的行。但大多数情况下,您的读取缓冲区较少,因此您的读取缓冲区只读取可见字符,您需要自己对字符串进行空终止。


我还尝试重写您的处理循环,以便将第一次初始调用作为它的一部分 strtok。编写起来并不总是很方便,但通常在循环之前或之后看到一段代码几乎与循环内的内容相同,这让我觉得有更好的逻辑方法。

【讨论】:

    【解决方案3】:

    非常感谢您富有洞察力的回复。

    最后我只是重写了整个内容,因为它看起来不像我预期的那么干净。 由于来自strtok 的手册指定返回值NULL 如果无法提取令牌,这就是我最终得到的结果:

    long number;
    item = strtok(line, delim);
    while (item != NULL) {
           number = strtol(item, &rest, 10);
           if (*rest == 0) {
               zeroOne(number);
           }
           else {
               fprintf(stderr, "\"%s\": not a non-negative int.\n", item);
           }
           item = strtok(NULL, delim);
    }   
    

    似乎是一种更简洁的方法,并且确实考虑了 strtok 的返回值NULL 在尝试进入我之前的while 循环之前返回的事实。然后,今天早上我回到这里并阅读了您的回复:)

    关于您将\0 硬编码输入到以下行的后续问题:这是否意味着我的最后一个strtok 实际上输出\0 令牌并尝试进入循环,或者这样做在我上次介绍的角色之后立即到达实体时返回NULL? 作为惯例,当使用read()(或者可能是其他读取函数,例如fgets() 时,我是否应该始终尝试将\0 硬编码到读取的行以允许其他函数检查EOL / EOF ?

    如果其他人在使用这些功能(strtokstrtol)时遇到问题,我建议您查看本网站上发布的这两个问题:

    关于strtol的第二个参数:Strtol second argument

    关于strtok的输出:Cannot concatenate strtok's output variable. strcat and strtok

    【讨论】:

    • 抱歉耽搁了,但我们没有收到有关新答案的通知(您收到通知,因为您发布了问题)。我只是注意到,因为您不接受我的回答 :) 关于您的问题,strtok 将在找不到更多令牌并且已到达字符串末尾时返回 NULL。所以 strtok 不会返回\0。使用字节读取函数添加尾随 \0 确实是惯例,但这取决于函数,所以不要认为所有函数都会为您执行此操作是一种习惯(例如,strcpy 会,而strncpy 可能不会)。很高兴你解决了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-11
    • 2016-04-19
    • 1970-01-01
    • 1970-01-01
    • 2017-11-12
    • 1970-01-01
    相关资源
    最近更新 更多