atof和非空终止字符数组

3
using namespace std;
int main(int argc, char *argv[]) {
    char c[] = {'0','.','5'};
    //char c[] = "0.5";
    float f = atof(c);
    cout << f*10;
    if(c[3] != '\0')
    {
        cout << "YES";
    }
}

输出:5YES

atof函数是否也适用于非空结尾的字符数组?如果是,它如何知道在哪里停止?


1
我无法重现它 - 我认为这是未定义的行为。 - masoud
2
@MM。幸运的是,它只是格式化了我的硬盘。 - Luchian Grigore
@MM。这是未定义的行为,很有可能在c之后的内存中存在空终止符。 - Overv
@LuchianGrigore 如果编译器删除源文件导致 UB,那将是一件有趣的事情 :D - stefan
@stefan,听起来像是恐慌排序 :) - default
8个回答

5

atof函数可以处理非空结尾的字符数组吗?

不行std::atof 要求输入为以零结尾的字符串。如果未能满足此前提条件,则可能会导致未定义行为

未定义行为意味着任何事情都可能发生,包括程序似乎正常工作。 在这里发生的是,由于偶然性,您的数组最后一个元素之后的内存字节无法解释为浮点数的表示形式的一部分,这就是为什么您的 std::atof 实现停止的原因。 但这是不能依赖的。

您应该按照以下方式修复程序:

char c[] = {'0', '.', '5', '\0'};
//                         ^^^^

1
实际上,数组后面没有 '\0'。请注意代码中对此的测试(也会引发 UB)。atof 会在任何无法解释为浮点数表示的字符处停止。 - jrok
2
@jrok:然而,并没有要求它在遇到不属于浮点数的字符时停止。这只是实现问题的质量。一个糟糕的atof解释肯定可以在其输入上调用strlen,即使这在某些情况下可能会使它变慢1000倍。 - R.. GitHub STOP HELPING ICE

2
不,atof不能处理非空结尾数组:它会在发现传入的数组末尾后面的零时停止。传递没有结尾的数组是未定义的行为,因为它会导致函数读取超出数组末尾的内容。在您的示例中,该函数很可能已经访问了您为f分配的字节(虽然这并不确定,因为f不需要在内存中跟随c[])。
char c[] = {'0','.','5'};
char d[] = {'6','7','8'};
float f = atof(c); // << Undefined behavior!!!
float g = atof(d); // << Undefined behavior!!!
cout << f*10;

上述代码输出5.678,说明已经读取了数组结尾以外的位置。

1

atof()需要一个以null结尾的字符串。

如果您有一个需要转换的字符串没有以null结尾,您可以尝试根据每个字符的值将其复制到目标缓冲区中,确保每个字符都是有效数字。例如:

char buff[64] = { 0 };

for( int i = 0; i < sizeof( buff )-1; i++ )
{
    char input = input_string[i];

    if( isdigit( input ) || input == '-' || input == '.' )
        buff[i] = input;
    else
        break;
}

double result = atof( buff );

1
请注意,C字符串并不意味着使用ASCII编码...。 - masoud
1
实际上,ASCIIZ 不是一个正确的术语。在 C 语言中,该术语只是“字符串”,它被定义为包括 null 终止的要求。否则,您可以将其澄清为“C 字符串”(即使用 C 的术语定义的字符串)或“以 null 结尾的字符串”。 - R.. GitHub STOP HELPING ICE

0
自从C++11以来,我们就有了std::stof。通过用std::stof替换atof,处理起来会更容易。
如果您总是传递已知大小的字符数组,则可以使用一个方便的包装器。

Live Demo

#include <fmt/core.h>
#include <type_traits>
#include <iostream>

// SFINAE fallback  
template<typename T, typename =
    std::enable_if< std::is_pointer<T>::value >
>
float charArrayToFloat(const T arr){  // Fall back for user friendly compiler errors
    static_assert(false == std::is_pointer<T>::value, "`charArrayToFloat()` dosen't allow conversion from pointer!");
    return -1;
}

// Valid for both null or non-null-terminated char array
template<size_t sz>
float charArrayToFloat(const char(&arr)[sz]){
    // It doesn't matter whether it's null terminated or not
    std::string str(arr, sz);
    return std::stof(str);
}


int main() {
    char number[4] = {'0','.','4','2'};
    float ret = charArrayToFloat(number);
    fmt::print("The answer is {}. ", ret);
    return 0;
}

输出:答案是0.42。


0
从 MSDN 对 atof() 函数的描述中可以得知(可能适用于其他编译器):
该函数在读取到第一个不能识别为数字的字符时停止读取输入字符串。这个字符可以是终止字符串的空字符 ('\0' 或 L'\0')。

0

它必须以0结尾,或者文本必须包含不属于数字的字符。


0

atof函数是否也适用于非空终止的字符数组?

不,该函数需要一个指向空终止字符串的指针。如果没有这样做,例如通过传递指向非空终止字符串(或非空终止字符数组)的指针,则会导致未定义行为

未定义行为意味着任何事情1都可能发生,包括但不限于程序产生您期望的输出。但是永远不要依赖(或基于)具有未定义行为的程序的输出结果。

因此,您看到(或可能看到)的输出是未定义行为的结果。正如我所说,不要依赖于具有UB的程序的输出。程序可能会崩溃。

因此,使程序正确的第一步是消除UB。只有在这之后,您才能开始推理程序的输出。


1更准确地定义未定义行为,请参见this,其中提到:程序的行为没有任何限制


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接