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

问题描述 投票:0回答:6

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

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

自然地,我们会准确地按照时钟上显示的时间和分钟来书写。因此,例如,6:30 将是

CLOCK_TIME(6, 30)
。这一切都很好,直到有人试图输入
CLOCK_TIME(6, 08)
并且编译器吐出一个错误说
08
不是有效的八进制常量。我们尝试了一些解决方法,但它们导致了它们自己的问题。例如,将
min
替换为
1##min - 100
修复了该特定问题,但是宏将因任何不是 2 位十进制文字的输入而失败。有没有一种方法可以让
08
09
在宏中工作,同时也允许参数可以是单个数字、表达式或变量?

是的,我意识到不使用前导零是一种解决方案,但是让时间以它们在时钟上出现的方式出现可以提高可读性和可用性。

编辑:一个额外的约束是,如果时间是一个常量,它必须在编译时而不是运行时计算。

c c-preprocessor octal
6个回答
2
投票

正如@dimich所建议的,您可以附加科学记数法

e0
.
这将首先强制数字为双精度数,然后您可以将其类型转换回
int
.

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

这是有效的,因为

08e0
(double)8.000
一样有效,即使
08
是无效的八进制。

IDEOne Link


如果不介意运行时的开销,可以把

08
变成字符串
"08"
,然后用
strtol
解析成整数。

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

2
投票

任意数量的数字(在实现限制内)(但不是表达式或变量)的解决方案是

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 预处理器中。您可以编写自己的脚本来删除调用此宏的数字中的前导零,然后使用此脚本在编译之前处理源代码。


0
投票

有没有办法让 08 和 09 在宏中工作,同时允许参数可以是单个数字、表达式或变量

(强调我的)

不,绝对没有办法使用 C 预处理器来实现。


0
投票

考虑将传递给宏的值作为字符串中的数字,使用

gcc
可以进行从字符串到整数的转换,由于 statement expression:

#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 问题,但计算是在运行时完成的。


0
投票

但是如果输入不是 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编程语言的内部部分令人困惑。八进制数可能会让该语言的新用户感到困惑,但我发现这种宏用法会让世界其他地方的人感到困惑。我宁愿在宏附近留下评论以注意和了解八进制数。


-1
投票

只需添加前导

1
,然后在计算之前减去 10 或 100,具体取决于传入的
min
是否大于 100:

#define _CLOCK(x) (x - (x >= 100 ? 100 : 10))
#define CLOCK(x) _CLOCK(1##x)

#include <iostream>

int main() {
  std::cout << CLOCK(08); // 8
  std::cout << CLOCK(8);  // 8
}

将修复应用于您的示例:

#define _CLOCK_TIME(hr, min) (s32)(((hr) * 60 + (min - (min > 100 ? 100 :  10))) * 0x10000 / (24 * 60))
#define CLOCK_TIME(hr, min) _CLOCK_TIME(hr, 1##min)

#include <iostream>
typedef int s32;

int main() {
  std::cout << (CLOCK_TIME(6, 8) == CLOCK_TIME(6, 08)); // 1
}

请注意,这仍然很脆弱,并且会在无效输入时失败,但会在 00..59 范围内的所有内容上正常工作。你总是可以在输入上添加静态断言。

© www.soinside.com 2019 - 2024. All rights reserved.