如何在C语言中将表达式转换为字符串

9
有没有办法在C语言中在字符串化之前对表达式进行评估?
例如:
#define stringify(x)  #x
...
const char * thestring = stringify( 10 * 50 );

问题在于我想获取
const char * thestring = "500";

而不是

const char * thestring = "10 * 50";

这可以做到吗?


抱歉,我无法克制自己同时说出RTFM和LMGTFY xD。 - fortran
7个回答

10

C 预处理器无法完成此操作,因此请改用 snprintf

char *stringify(int n) {
   char *res = malloc(12);
   snprintf(res, 12, "%d", n);
   return res;
}

用法

const char *thestring = stringify(10 * 50);

注意事项

为了简单起见,我省略了错误控制和 free


2
放弃“据我所知”的说法。预处理器在编译器之前运行。表达式在运行时语义上计算(尽管编译器应该在编译时将其优化为单个常量)。 - T.E.D.
由于您省略了 free,@Andrew的代码可能会导致内存泄漏。 - SLaks
@SLaks:这就是为什么我添加了一个NB - dfa
2
注意:10个字节不够。你至少需要12个字节,包括零终止符和负整数的前导“-”。 - Enno

5

也许你不会喜欢表达式的呈现方式,是的,这是可能的,但需要以非常折衷的方式来完成——你需要创建一种独立的函数语言,由预处理器“运行”。证明如下:

$ cvs -d:pserver:anonymous@chaos-pp.cvs.sourceforge.net:/cvsroot/chaos-pp login 
$ cvs -z3 -d:pserver:anonymous@chaos-pp.cvs.sourceforge.net:/cvsroot/chaos-pp co -P chaos-pp
$ cvs -z3 -d:pserver:anonymous@chaos-pp.cvs.sourceforge.net:/cvsroot/chaos-pp co -P order-pp
$ cd order-pp/example
$ grep -A 6 'int main' fibonacci.c
int main(void) {
   printf
     ("The 500th Fibonacci number is "
      ORDER_PP(8stringize(8to_lit(8fib(8nat(5,0,0)))))
      ".\n");
   return 0;
}
$ cpp -I../inc fibonacci.c 2>/dev/null | grep -A 6 'int main' 
int main(void) {
   printf
     ("The 500th Fibonacci number is "
      "139423224561697880139724382870407283950070256587697307264108962948325571622863290691557658876222521294125"
      ".\n");
   return 0;
}

在这个例子中,我们使用新制作的预处理器运行纯函数语言来计算第500个斐波那契数,并将其字符串化以提供给C编译器。
当然,我非常怀疑这不是你在实践中会使用的东西,这是对预处理器的极度滥用,但我认为这是一个非常发人深省的技巧。(是的,没有像这样的奇特理论扭曲,这是不可能的)。

4
我猜你在脚本语言方面的经验比C语言多。
对于纯编译语言(如C),你需要了解多个阶段:预处理、编译、链接和运行。
首先运行预处理器。这是宏展开的地方。此时,它的内容为“10 * 50”。没什么可以做的。
宏预处理完成后,编译器将程序转换为对象文件。
当编译器完成每个源文件时,链接器会介入并将它们全部组合在一起。
最后,当用户准备好时,他们会执行您的程序。从语义上讲,这是计算10 * 50的时候。(实际上,大多数编译器将识别到这将始终是相同的值,并将其替换为500,但这是一个实现细节)。
脚本语言喜欢模糊所有这些界限,所以我可以看出某些人可能会感到困惑。

1

预处理器宏在编译器之前运行。根据定义,无法完全做到你想要的。

要在运行时将数字转换为字符串,请调用itoa函数,像这样:

char thestring[8];

itoa(10 * 50, thestring, 10);

请注意,此代码将thestring声明为数组,而不是指针。有关C语言中的内存管理的更多信息,请阅读相关资料。

4
itoa 不是标准的 C 函数。 - caf

0

3
不,他并不这样认为。更仔细地阅读问题。 - SLaks

-1
你可以编写一个脚本(Perl?)作为预处理器,识别要评估的字符串,对它们进行评估,然后调用“已评估”文件上的真正cpp。
也许这样可以行得通。

-2

正如其他回答所说,这不能通过C预处理器完成。这是C的许多缺点之一,而这些缺点都被C++解决了。这是可以使用模板元编程以非常优雅的方式实现的事情。

要在编译时计算算术表达式:

#include <boost/mpl/arithmetic.hpp>
namespace mpl = boost::mpl;
int main(int argc, char *argv[]) {
  const int n = mpl::multiplies<mpl::int_<10>, mpl::int_<50> >::value;
  return 0;
}

我在 Boost 邮件列表档案中找到了一个 字符串格式化元函数。这个版本可以将一个整数(例如上面计算的那个)转换为您选择的进制的字符串:

#include <boost/mpl/string.hpp>
#include <boost/mpl/vector_c.hpp>
#include <boost/mpl/at.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/identity.hpp>
#include <boost/mpl/push_back.hpp>
namespace mpl = boost::mpl;
struct itoa_ct
{
  // radix for _itoa() goes up to 36, but only bother with 16 here
  typedef mpl::vector_c<char
    ,'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'
    > radix_t;
  template <int Radix, unsigned int Quotient>
    struct radix_convert
    {
      typedef typename mpl::push_back<
        typename radix_convert<Radix, Quotient / Radix>::type
        , mpl::char_<mpl::at_c<radix_t, Quotient % Radix>::type::value>
        >::type type;
    };
  template <int Radix>
    struct radix_convert<Radix, 0>
    {
      typedef mpl::string<> type;
    };
  template <int I, int Radix = 10>
    struct apply
    {
      // All bases != 10 consider I as unsigned
      typedef typename radix_convert<
        Radix, static_cast<unsigned int>((Radix == 10 && I < 0) ? -I : I)
        >::type converted_t;
      // Prefix with '-' if negative and base 10
      typedef typename mpl::if_<
        mpl::bool_<(Radix == 10 && I < 0)>
        , mpl::push_front<converted_t, mpl::char_<'-'> >
        , mpl::identity<converted_t>
        >::type::type type;
    };
};

将这两部分组合起来,你的表达式就变成了:
const char *thestring = mpl::c_str<itoa_ct::apply<mpl::multiplies<mpl::int_<10>, mpl::int_<50> >::value>::type>::value;

......这一切最终在编译时都会变成一个常量字符串"500"而已 :-)


这很好,但这可能不是原帖作者正在寻找的。 - SLaks
不,可能不行,只是想指出你可以用C++来解决这些问题 :-) 正如Andrew Y展示的那样,如果你写一个完全基于宏的语言,你也可以在C中做到这种事情。 - David Claridge
1
这是一个非常复杂的答案,与原问题毫无关系,尽管我理解意图肯定是最好的。 - alecov

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