避免在C宏中解释为八进制

hiz5n14c  于 2023-04-19  发布在  其他
关注(0)|答案(6)|浏览(157)

在我正在开发的一个应用程序中,我们有一个宏将时钟时间转换为一个短整数的内部表示:

#define CLOCK_TIME(hr, min) (s32)(((hr) * 60 + (min)) * 0x10000 / (24 * 60))

自然地,我们会按照时钟上显示的时间和分钟来写。所以,例如,6:30将是CLOCK_TIME(6, 30)。这一切都很好,直到有人试图输入CLOCK_TIME(6, 08),编译器吐出一个错误,说08不是一个有效的八进制常数。我们尝试了一些变通方法,但它们导致了自己的问题。例如,用1##min - 100替换min解决了这个特定的问题,但这样宏将无法使用任何不是2位十进制文字的输入。有没有一种方法可以允许0809在宏中工作,同时允许参数可以是个位数、表达式或变量?
是的,我意识到不使用前导零是一种解决方案,但是让时间以时钟上的方式显示可以提高可读性和可用性。
编辑:一个额外的约束是,如果时间是一个常数,它 * 必须 * 在编译时计算,而不是运行时。

ego6inou

ego6inou1#

任意位数的解(在执行限度内)(但不是表达式或变量)是1##min*2 - 2##min。这可以被认为是(10p+x)·2 −(2·10p+x),其中 px 中的未知位数,它等于(2·10p+2x)−(2·10p+x)= 2xx = x。这避免了浮点问题。
例如,23将被123*2 - 223替换,其等于23。08将是108*2 - 208 = 8,而8将是18*2 - 28 = 8。
处理数字、表达式和变量的一个解决方案是在C之外编写自己的脚本。人类不需要将他们的程序限制在C预处理器中。您可以编写自己的脚本来删除此宏调用中数字的前导零,然后在编译源代码之前使用此脚本处理源代码。

rwqw0loc

rwqw0loc2#

正如@dimich建议的那样,您可以添加科学计数法e0
这将首先强制数字为双精度型,然后您可以将其类型转换回int

#define CLOCK_TIME(hr, min) (int32_t)(((hr) * 60 + (int32_t)(min##e0)) * 0x10000 / (24 * 60))

这是因为08e0作为(double)8.000是有效的,即使08是无效的八进制。

k5ifujac

k5ifujac3#

有没有一种方法可以让08和09在宏中工作,同时也允许参数可以是个位数,* 表达式或变量 *?
(强调我的)
不,绝对没有办法使用C预处理器来实现这一点。

e1xvtsh3

e1xvtsh34#

但如果输入不是2位十进制文字,则宏将失败
下面的代码:

#include <stdio.h>
#define POW10I(x) ( \
    x == 0 ? 1 : \
    x == 1 ? 1 : \
    x == 2 ? 10 : \
    x == 3 ? 100 : \
    x == 4 ? 1000 : \
    x == 5 ? 10000 : \
    x == 6 ? 100000 : \
    x == 7 ? 1000000 : \
    x == 8 ? 10000000 : \
    x == 9 ? 100000000 : \
    -1 )
#define TOBASE10IN(one_and_x, strx)   (one_and_x - POW10I(sizeof(strx)))
#define TOBASE10(x) TOBASE10IN(1##x, #x)

int main() {
    printf("%d\n", TOBASE10(0123456));
}

输出123456。
有没有一种方法可以让08和09在宏中工作,同时也允许参数可以是个位数,表达式或变量?
不知道
如果你愿意使用运行时,你可以做任何事情,只要解析表达式的整个字符串表示,检查它是否是一个八进制数,然后采取不同的分支。

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
bool isdigits(const char str[]) {
    for (; *str; str++) {
        if (!isdigit(*str)) {
            return false;
        }
    }
    return true;
}
int tobase10(int x, const char xstr[]) {
    if (xstr[0] == '0' && isdigits(xstr)) {
        return strtol(xstr, NULL, 10);
    }
    return x;
}
#define TOBASE10(x) tobase10(x, #x)

int main() {
    printf("%d\n", TOBASE10(0123456));
    int a = 10;
    printf("%d\n", TOBASE10(a + a));
}

我的看法:我觉得你想改变C语言的一个内部部分是令人困惑的。八进制数字可能会让语言的新用户感到困惑,但我发现这样的宏用法会让世界其他地方感到困惑。我宁愿在宏旁边留下评论来关注和学习八进制数字。

yhqotfr8

yhqotfr85#

考虑到传递给宏的值是数字,使用gcc可以从字符串转换为整数,由于statement expression,可以消除前导0:

#define CLOCK_TIME(hr, min) ({ \
                   char __hr[3] = #hr, __min[3] = #min; \
                   int __hrv = 0, __minv = 0, __mul, __i; \
                   __mul = 1;__i = sizeof(#hr) - 2; \
                   while (__i >= 0) { __hrv += (__hr[__i --] - '0') * __mul; __mul *= 10; }  \
                   __mul = 1; __i = sizeof(#min) - 2;  \
                   while (__i >= 0) { __minv += (__min[__i --] - '0') * __mul; __mul *= 10; } \
                   (int)(((__hrv) * 60 + (__minv)) * 0x10000 / (24 * 60)); \
                            })

这解决了前导0的问题,但计算是在运行时完成的,并且这仅适用于传递给宏的数字(没有变量或表达式)。

3gtaxfhh

3gtaxfhh6#

将其定义为宏而不是函数,只有当你打算在编译时计算这个数字时才有意义。因此,假设宏在编译时 * 不 * 获得传递的运行时变量,而只是数字...
...那么就有可能变出一些邪恶的魔法宏语言(不推荐),以hh:mm作为输入,但也允许h:mm(但不允许hh:m)。调用方看起来像这样:

#include <inttypes.h>
#include <stdio.h>

int main (void)
{
  printf("%" PRIi32 "\n", CLOCK_TIME(6:30));  // 17749
  printf("%" PRIi32 "\n", CLOCK_TIME(6:08));  // 16748
  printf("%" PRIi32 "\n", CLOCK_TIME(12:00)); // 32768
}

邪恶的宏看起来像这样:

#define CALCULATE_STUFF(hr, min) (int32_t)(((hr) * 60 + (min)) * 0x10000 / (24 * 60))

#define STRLEN(str) (sizeof(str) - 1)
#define STR(arg) #arg

#define CLOCK_TIME(arg)                                              \
  STRLEN(STR(arg)) < 4 ? 0 :                                         \
  STRLEN(STR(arg)) > 5 ? 0 :                                         \
  STRLEN(STR(arg))==4 && STR(arg)[1]!=':' ? 0 :                      \
  STRLEN(STR(arg))==5 && STR(arg)[2]!=':' ? 0 :                      \
  CALCULATE_STUFF(                                                   \
    STRLEN(STR(arg))==4 ? (STR(arg)[0]-'0') :                        \
                          (STR(arg)[0]-'0')*10 + (STR(arg)[1]-'0')   \
  ,                                                                  \
    STRLEN(STR(arg))==4 ? (STR(arg)[2]-'0')*10 + (STR(arg)[3]-'0') : \
                          (STR(arg)[3]-'0')*10 + (STR(arg)[4]-'0')   \
  )

说明:

  • 如果不正确,此宏返回0,但也可以返回其他整数以表示出错。
  • 错误处理基于“链接”多个?:操作符,其优先级非常低,以至于您可以将几乎任何内容作为操作数写入。它的工作方式类似于一堆静态Assert(如果您喜欢,可以将宏的结果传递给_Static_assert)。
  • STR辅助宏将输入转换为字符串文字,如"hh:mm",整个宏都在字符串上工作。
  • 因此,我们可以通过获取字符串字面量的大小来进行编译时“strlen”调用,减1表示空终止符。
  • 首先,宏通过调用宏STRLEN检查是否有足够的数字,然后检查:是否在允许的位置之一。
  • 一旦输入格式被认为是正确的,实际的计算(我没有检查正确性)在CALCULATE_STUFF中执行。
  • CALCULATE_STUFF有两个参数,根据使用的输入格式,它会进行字符到整数的转换,将第一个数字乘以10。

这个宏的开销为零,CLOCK_TIME(6:30)等调用在编译时被整数替换。

相关问题