【问题标题】:Conversion of string constant to numeric value using C使用C将字符串常量转换为数值
【发布时间】:2019-07-29 08:52:48
【问题描述】:

我编写了一个 C 程序,它使用两种不同的算法将表示数值的字符串常量转换为其整数值。由于某些原因,第一个算法 atoi() 不能在大值上正确执行,而第二个算法 atoi_imp() 工作正常。这是优化问题还是其他错误?问题是第一个函数使程序的进程因错误而终止。

#include <stdio.h>
#include <string.h>

unsigned long long int atoi(const char[]);
unsigned long long int atoi_imp(const char[]);

int main(void) {
    printf("%llu\n", atoi("9417820179"));
    printf("%llu\n", atoi_imp("9417820179"));
    return 0;
}

unsigned long long int atoi(const char str[]) {
    unsigned long long int i, j, power, num = 0;
    for (i = strlen(str) - 1; i >= 0; --i) {
        power = 1;
        for (j = 0; j < strlen(str) - i - 1; ++j) {
            power *= 10;
        }
        num += (str[i] - '0') * power;
    }
    return num;
}

unsigned long long int atoi_imp(const char str[]) {
    unsigned long long int i, num = 0;
    for (i = 0; str[i] >= '0' && str[i] <= '9'; ++i) {
        num = num * 10 + (str[i] - '0');
    }
    return num;
}

【问题讨论】:

  • 没有正确执行是什么意思?作为问题描述,这是一个毫无意义的短语。您遇到了什么具体问题
  • @KenWhite 问题是第一个函数使程序的进程以错误终止。
  • 别告诉我。 :-) 相反,edit 您的问题并将其放在人们可以看到的地方。埋在评论混乱中是没有好处的。

标签: c algorithm debugging optimization


【解决方案1】:

好的,你的 atoi 函数至少有一个问题。
您正在循环一个无符号值并检查其较大的是否等于零,这应该是下溢。

最简单的解决方法是索引移位,即:

unsigned long long int my_atoi(const char str[]) {
    unsigned long long int i, j, power, num = 0;
    for (i = strlen(str); i != 0; --i) {
        power = 1;
        for (j = 0; j < strlen(str) - i; ++j) {
            power *= 10;
        }
        num += (str[i-1] - '0') * power;
    }
    return num;
}

【讨论】:

  • 不错,据我所知,这似乎是唯一的问题。
  • 无符号下溢不是“标准未定义”。无符号计算被明确定义并且只是换行。每6.2.5 Types, paragraph 9:“无法由生成的无符号整数类型表示的结果以比结果类型可以表示的最大值大一的数字为模减少。”代码的问题似乎是一个无符号值总是大于或等于零,所以循环永远不会终止。
  • @AndrewHenle 谢谢我不知道如何正确表达我会编辑它。
  • @AndrewHenle 未定义的行为是下溢的结果。他后来尝试访问 str[i] - 因为 i 下溢,所以没有反弹。
  • 你应该只计算一次strlen(str)
【解决方案2】:

atoi 是 C 标准库的一部分,签名为 int atoi(const char *);

您正在声明具有该名称的函数存在,但给它不同的返回类型。请注意,在 C 中,函数名是唯一重要的事情,工具链只能信任您在源代码中所说的内容。如果你像这里一样对编译器撒谎,那么所有的赌注都没有了。

您应该为自己的实现选择不同的名称以避免出现问题。


正如@pmg 所研究的,C 标准(链接到C99.7.1.3)说,将 C 标准库中的名称用于您自己的全局符号(函数或全局变量)是明确的未定义行为。当心鼻恶魔!

【讨论】:

  • 我重读了C99 7.1.3,发现你是对的。使用你自己的atoi() 是UB。很抱歉造成混乱,谢谢你让我理顺。以前错误的 cmets 已删除。
  • 我投了赞成票。然后改变了主意,投了反对票。然后我又改变了主意。我对您的回答有疑问的原因是 NotT90 被接受,这向我表明问题不是由尝试重新定义 atoi 引起的。因此,在我看来,您的回答应该只是对 OP 的评论。但后来我改变了主意,原因有两个:(1)你的建议很好,可能会在评论中丢失;(2)我因投票而失去声誉。不知道为什么会发生损失,但更强的原因是#1,所以#2有点无关紧要。
  • @jeff6times7 啊,公平的'nuff。是的,我认为有一个表明代码存在问题的答案是可以的,并且可能是正确的答案,即使事实证明它不是所问问题的根源。跨度>
【解决方案3】:

为时已晚,但可能会有所帮助。我为基数 10 做了,如果你改变基数,你需要注意如何计算数字 0,*p-'0'

我会使用Horner's rule 来计算值。

#include <stdio.h>
void main(void)
{
  char *a = "5363", *p = a;
  int unsigned base = 10;
  long unsigned x = 0;
  while(*p) {
    x*=base;
    x+=(*p-'0');
    p++;
  }
  printf("%lu\n", x);
}

【讨论】:

    【解决方案4】:

    您的函数有一个无限循环:由于i 是无符号的,i &gt;= 0 始终为真。

    可以通过不同的方式进行改进:

    • 您应该只计算一次str 的长度。 strlen() 并不便宜,它必须扫描字符串,直到找到空终止符。编译器并不总是能够优化掉对同一参数的冗余调用。

    • power 可以增量计算,无需嵌套循环。

    • 您不应使用名称 atoi,因为它是 C 库中的标准函数。除非您准确且正确地实现其规范,否则您应该使用不同的名称。

    这是一个更正和改进的版本:

    unsigned long long int atoi_power(const char str[]) {
        size_t i, len = strlen(str);
        unsigned long long int power = 1, num = 0;
        for (i = len; i-- > 0; ) {
            num += (str[i] - '0') * power;
            power *= 10;
        }
        return num;
    }
    

    这样修改,功能应该和atoi_imp版本的性能差不多。但是请注意,它们没有实现相同的语义。 atoi_pow 必须给出一串数字,而 atoi_imp 可以有尾随字符。

    事实上,atoi_impatoi_pow 都没有实现 atoi 扩展以处理更大的无符号整数的规范:

    • atoi 忽略任何前导空白字符,
    • atoi 接受可选符号,'+''-'
    • atoi 消耗所有后面的十进制数字,溢出时的行为未定义。
    • atoi 忽略非十进制数字和尾随字符。

    鉴于这些语义,自然实现或atoiatoi_imp 的实现,并带有额外的测试。请注意,即使您可以用来实现函数的 strtoull() 也可以处理空格和可选符号,尽管负值的转换可能会产生令人惊讶的结果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-03
      • 1970-01-01
      • 2019-11-04
      • 2012-09-20
      • 2017-11-06
      相关资源
      最近更新 更多