使用预处理器的 C 中两个数组的笛卡尔积
Cartesian product of two arrays in C using preprocessor
我想用另外两个数组的笛卡尔积填充一个数组。它们包含 4 位值,应位于结果数组中元素的低半字节和高半字节。
示例:
const char a1[2] = {0x1, 0x2};
const char a2[2] = {0x3, 0x4};
const char a21[4] = {0x31, 0x32, 0x41, 0x42};
我知道我可以在 运行 时构造它,但我想在编译时生成数组。由于程序是用C编写的,我认为预处理器是唯一的选择。
实际数组会比示例中的大,它们将包含 16 个元素,结果将包含 256 个元素。这就是为什么我想避免自己输入值。
我需要它来加速 16 位微控制器上的加密例程,因为它可以处理比 4 位半字节更大的数据。
我认为如果它适用于 2 或 3,我将能够使解决方案适应 16 种元素。
您需要的是一小段代码来生成头文件。像这样:
genheader.c:
#include <stdio.h>
const char a1[] = {0x1, 0x2};
const char a2[] = {0x3, 0x4};
int main()
{
int i, j, first;
printf("const char a12[] = {");
first = 1;
for (j=0;i<sizeof(a2);j++) {
for (i=0;i<sizeof(a1);i++) {
if (first) {
first = 0;
} else {
printf(", ");
}
printf("0x%d%d", a2[j], a1[i]);
}
}
printf("};\n");
}
然后运行:
gcc -o genheader genheader.c
./genheader > header.h
header.h:
const char a12[] = {0x31, 0x32, 0x41, 0x42};
如果您的目标是创建可读和可维护的代码,则没有任何明智的方法可以使用预处理器解决此问题。您唯一可以做的就是:
#define VAL1_1 0x1
#define VAL1_2 0x2
#define VAL2_1 0x3
#define VAL2_2 0x4
static const char a1[2] = {VAL1_1, VAL1_2};
static const char a2[2] = {VAL2_1, VAL2_2};
static const char a21[4] =
{
VAL1_1 | (VAL2_1 << 4),
VAL1_2 | (VAL2_1 << 4),
VAL1_1 | (VAL2_2 << 4),
VAL1_2 | (VAL2_2 << 4),
};
但是这段代码不是很通用或可维护。如果这还不够通用,那么唯一明智的解决方案是根据包含原始输入数据的文件在外部生成源代码。程序员往往会忘记他们知道编程 :) 编写一个以给定格式吐出文本文件的程序应该不是什么大问题。
你真的不应该 运行 关闭并使用宏创建其他宏等来创建一些疯狂的预处理器。那只能以糟糕的方式结束。
好的,我在网上找到的解决方案来了。在我看来,它包含大量疯狂的宏,但它最终完成了工作。当然,用Excel或者脚本生成数组内容更容易理解,但为了完整起见,我决定在这里post宏解决。
#define CAT(x, y) CAT_I(x, y)
#define CAT_I(x, y) x ## y
#define APPLY(macro, args) APPLY_I(macro, args)
#define APPLY_I(macro, args) macro args
#define STRIP_PARENS(x) EVAL((STRIP_PARENS_I x), x)
#define STRIP_PARENS_I(...) 1,1
#define EVAL(test, x) EVAL_I(test, x)
#define EVAL_I(test, x) MAYBE_STRIP_PARENS(TEST_ARITY test, x)
#define TEST_ARITY(...) APPLY(TEST_ARITY_I, (__VA_ARGS__, 2, 1))
#define TEST_ARITY_I(a,b,c,...) c
#define MAYBE_STRIP_PARENS(cond, x) MAYBE_STRIP_PARENS_I(cond, x)
#define MAYBE_STRIP_PARENS_I(cond, x) CAT(MAYBE_STRIP_PARENS_, cond)(x)
#define MAYBE_STRIP_PARENS_1(x) x
#define MAYBE_STRIP_PARENS_2(x) APPLY(MAYBE_STRIP_PARENS_2_I, x)
#define MAYBE_STRIP_PARENS_2_I(...) __VA_ARGS__
#define M1(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16, a) _1 + (a << 4),\
_2 + (a << 4),\
_3 + (a << 4),\
_4 + (a << 4),\
_5 + (a << 4),\
_6 + (a << 4),\
_7 + (a << 4),\
_8 + (a << 4),\
_9 + (a << 4),\
_10 + (a << 4),\
_11 + (a << 4),\
_12 + (a << 4),\
_13 + (a << 4),\
_14 + (a << 4),\
_15 + (a << 4),\
_16 + (a << 4),
#define M2(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16, a) M1(a, _1)\
M1(a, _2)\
M1(a, _3)\
M1(a, _4)\
M1(a, _5)\
M1(a, _6)\
M1(a, _7)\
M1(a, _8)\
M1(a, _9)\
M1(a, _10)\
M1(a, _11)\
M1(a, _12)\
M1(a, _13)\
M1(a, _14)\
M1(a, _15)\
M1(a, _16)
#define M3(a2, a1) M2(a2, STRIP_PARENS(a1))
#define M4(a2, a1) M3(STRIP_PARENS(a2), a1)
// source data (arrays to be combined)
// if anyone is interested, they represent S-boxes of GOST 28147-89 encryption algorithm.
#define s1 (4, 0xA, 9, 2, 0xD, 8, 0, 0xE, 6, 0xB, 1, 0xC, 7, 0xF, 5, 3)
#define s2 (0xE, 0xB, 4, 0xC, 6, 0xD, 0xF, 0xA, 2, 3, 8, 1, 0, 7, 5, 9)
#define s3 (5, 8, 1, 0xD, 0xA, 3, 4, 2, 0xE, 0xF, 0xC, 7, 6, 0, 9, 0xB)
#define s4 (7, 0xD, 0xA, 1, 0, 8, 9, 0xF, 0xE, 4, 6, 0xC, 0xB, 2, 5, 3)
#define s5 (6, 0xC, 7, 1, 5, 0xF, 0xD, 8, 4, 0xA, 9, 0xE, 0, 3, 0xB, 2)
#define s6 (4, 0xB, 0xA, 0, 7, 2, 1, 0xD, 3, 6, 8, 5, 9, 0xC, 0xF, 0xE)
#define s7 (0xD, 0xB, 4, 1, 3, 0xF, 5, 9, 0, 0xA, 0xE, 7, 6, 8, 2, 0xC)
#define s8 (1, 0xF, 0xD, 0, 5, 7, 0xA, 4, 9, 2, 3, 0xE, 6, 0xB, 8, 0xC)
const BYTE s21[256] = {M4(s2, s1)};
const BYTE s43[256] = {M4(s4, s3)};
const BYTE s65[256] = {M4(s6, s5)};
const BYTE s87[256] = {M4(s8, s7)};
使用 Chaos 和具有良好预处理器的编译器(例如 GCC):
#include <stdio.h>
#include <stdlib.h>
#include <chaos/preprocessor/algorithm/for_each_product.h>
#include <chaos/preprocessor/recursion/expr.h>
#include <chaos/preprocessor/seq/core.h>
#include <chaos/preprocessor/seq/elem.h>
#include <chaos/preprocessor/seq/enumerate.h>
#define A(l, h) \
const unsigned char \
a1[] = { CHAOS_PP_SEQ_ENUMERATE(l) }, \
a2[] = { CHAOS_PP_SEQ_ENUMERATE(h) }, \
a21[] = { \
CHAOS_PP_SEQ_ENUMERATE( \
CHAOS_PP_EXPR(CHAOS_PP_FOR_EACH_PRODUCT( \
B, \
((CHAOS_PP_SEQ) l) \
((CHAOS_PP_SEQ) h) \
)) \
) \
}; \
/**/
#define B(s, seq) \
(CHAOS_PP_SEQ_ELEM(0, seq) | (CHAOS_PP_SEQ_ELEM(1, seq) << 4)) \
/**/
A((0x1)(0x2), (0x3)(0x4))
#undef A
#undef B
int main() {
for (int i = 0; i != sizeof(a21) / sizeof(unsigned char); ++i) {
printf("0x%x\n", a21[i]);
}
return EXIT_SUCCESS;
}
您可以对每个数组的内容使用 Xmacro。
它与其他答案类似,但不需要额外的库,而且很容易掌握。
#define A(X, ...) \
X(1, __VA_ARGS__) \
X(2, __VA_ARGS__)
#define B(X, ...) \
X(3, __VA_ARGS__) \
X(4, __VA_ARGS__)
#define SIMPLE(VAL,...) 0x ##VAL,
#define JOIN(X,Y,...) 0x ## Y ## X,
#define EXPAND_A(...) A(JOIN, __VA_ARGS__)
const char a1[2] = { A(SIMPLE,) };
const char a2[2] = { B(SIMPLE,) };
const char a21[4] = { B(EXPAND_A,) };
生产:
const char a1[2] = { 0x1, 0x2, };
const char a2[2] = { 0x3, 0x4, };
const char a21[4] = { 0x31, 0x32, 0x41, 0x42, };
a1
和 a2
的初始值设定项是使用 SIMPLE
宏创建的。
笛卡尔积是通过在 B Xmacro 内扩展 A Xmacro 创建的,通过应用 JOIN
最终确定。
宏参数后的额外逗号用于使要求 ...
包含至少一个参数的 C99 兼容编译器静音。
令人惊讶的是,一个空标记是一个有效参数!
我想用另外两个数组的笛卡尔积填充一个数组。它们包含 4 位值,应位于结果数组中元素的低半字节和高半字节。
示例:
const char a1[2] = {0x1, 0x2};
const char a2[2] = {0x3, 0x4};
const char a21[4] = {0x31, 0x32, 0x41, 0x42};
我知道我可以在 运行 时构造它,但我想在编译时生成数组。由于程序是用C编写的,我认为预处理器是唯一的选择。
实际数组会比示例中的大,它们将包含 16 个元素,结果将包含 256 个元素。这就是为什么我想避免自己输入值。
我需要它来加速 16 位微控制器上的加密例程,因为它可以处理比 4 位半字节更大的数据。
我认为如果它适用于 2 或 3,我将能够使解决方案适应 16 种元素。
您需要的是一小段代码来生成头文件。像这样:
genheader.c:
#include <stdio.h>
const char a1[] = {0x1, 0x2};
const char a2[] = {0x3, 0x4};
int main()
{
int i, j, first;
printf("const char a12[] = {");
first = 1;
for (j=0;i<sizeof(a2);j++) {
for (i=0;i<sizeof(a1);i++) {
if (first) {
first = 0;
} else {
printf(", ");
}
printf("0x%d%d", a2[j], a1[i]);
}
}
printf("};\n");
}
然后运行:
gcc -o genheader genheader.c
./genheader > header.h
header.h:
const char a12[] = {0x31, 0x32, 0x41, 0x42};
如果您的目标是创建可读和可维护的代码,则没有任何明智的方法可以使用预处理器解决此问题。您唯一可以做的就是:
#define VAL1_1 0x1
#define VAL1_2 0x2
#define VAL2_1 0x3
#define VAL2_2 0x4
static const char a1[2] = {VAL1_1, VAL1_2};
static const char a2[2] = {VAL2_1, VAL2_2};
static const char a21[4] =
{
VAL1_1 | (VAL2_1 << 4),
VAL1_2 | (VAL2_1 << 4),
VAL1_1 | (VAL2_2 << 4),
VAL1_2 | (VAL2_2 << 4),
};
但是这段代码不是很通用或可维护。如果这还不够通用,那么唯一明智的解决方案是根据包含原始输入数据的文件在外部生成源代码。程序员往往会忘记他们知道编程 :) 编写一个以给定格式吐出文本文件的程序应该不是什么大问题。
你真的不应该 运行 关闭并使用宏创建其他宏等来创建一些疯狂的预处理器。那只能以糟糕的方式结束。
好的,我在网上找到的解决方案来了。在我看来,它包含大量疯狂的宏,但它最终完成了工作。当然,用Excel或者脚本生成数组内容更容易理解,但为了完整起见,我决定在这里post宏解决。
#define CAT(x, y) CAT_I(x, y)
#define CAT_I(x, y) x ## y
#define APPLY(macro, args) APPLY_I(macro, args)
#define APPLY_I(macro, args) macro args
#define STRIP_PARENS(x) EVAL((STRIP_PARENS_I x), x)
#define STRIP_PARENS_I(...) 1,1
#define EVAL(test, x) EVAL_I(test, x)
#define EVAL_I(test, x) MAYBE_STRIP_PARENS(TEST_ARITY test, x)
#define TEST_ARITY(...) APPLY(TEST_ARITY_I, (__VA_ARGS__, 2, 1))
#define TEST_ARITY_I(a,b,c,...) c
#define MAYBE_STRIP_PARENS(cond, x) MAYBE_STRIP_PARENS_I(cond, x)
#define MAYBE_STRIP_PARENS_I(cond, x) CAT(MAYBE_STRIP_PARENS_, cond)(x)
#define MAYBE_STRIP_PARENS_1(x) x
#define MAYBE_STRIP_PARENS_2(x) APPLY(MAYBE_STRIP_PARENS_2_I, x)
#define MAYBE_STRIP_PARENS_2_I(...) __VA_ARGS__
#define M1(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16, a) _1 + (a << 4),\
_2 + (a << 4),\
_3 + (a << 4),\
_4 + (a << 4),\
_5 + (a << 4),\
_6 + (a << 4),\
_7 + (a << 4),\
_8 + (a << 4),\
_9 + (a << 4),\
_10 + (a << 4),\
_11 + (a << 4),\
_12 + (a << 4),\
_13 + (a << 4),\
_14 + (a << 4),\
_15 + (a << 4),\
_16 + (a << 4),
#define M2(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16, a) M1(a, _1)\
M1(a, _2)\
M1(a, _3)\
M1(a, _4)\
M1(a, _5)\
M1(a, _6)\
M1(a, _7)\
M1(a, _8)\
M1(a, _9)\
M1(a, _10)\
M1(a, _11)\
M1(a, _12)\
M1(a, _13)\
M1(a, _14)\
M1(a, _15)\
M1(a, _16)
#define M3(a2, a1) M2(a2, STRIP_PARENS(a1))
#define M4(a2, a1) M3(STRIP_PARENS(a2), a1)
// source data (arrays to be combined)
// if anyone is interested, they represent S-boxes of GOST 28147-89 encryption algorithm.
#define s1 (4, 0xA, 9, 2, 0xD, 8, 0, 0xE, 6, 0xB, 1, 0xC, 7, 0xF, 5, 3)
#define s2 (0xE, 0xB, 4, 0xC, 6, 0xD, 0xF, 0xA, 2, 3, 8, 1, 0, 7, 5, 9)
#define s3 (5, 8, 1, 0xD, 0xA, 3, 4, 2, 0xE, 0xF, 0xC, 7, 6, 0, 9, 0xB)
#define s4 (7, 0xD, 0xA, 1, 0, 8, 9, 0xF, 0xE, 4, 6, 0xC, 0xB, 2, 5, 3)
#define s5 (6, 0xC, 7, 1, 5, 0xF, 0xD, 8, 4, 0xA, 9, 0xE, 0, 3, 0xB, 2)
#define s6 (4, 0xB, 0xA, 0, 7, 2, 1, 0xD, 3, 6, 8, 5, 9, 0xC, 0xF, 0xE)
#define s7 (0xD, 0xB, 4, 1, 3, 0xF, 5, 9, 0, 0xA, 0xE, 7, 6, 8, 2, 0xC)
#define s8 (1, 0xF, 0xD, 0, 5, 7, 0xA, 4, 9, 2, 3, 0xE, 6, 0xB, 8, 0xC)
const BYTE s21[256] = {M4(s2, s1)};
const BYTE s43[256] = {M4(s4, s3)};
const BYTE s65[256] = {M4(s6, s5)};
const BYTE s87[256] = {M4(s8, s7)};
使用 Chaos 和具有良好预处理器的编译器(例如 GCC):
#include <stdio.h>
#include <stdlib.h>
#include <chaos/preprocessor/algorithm/for_each_product.h>
#include <chaos/preprocessor/recursion/expr.h>
#include <chaos/preprocessor/seq/core.h>
#include <chaos/preprocessor/seq/elem.h>
#include <chaos/preprocessor/seq/enumerate.h>
#define A(l, h) \
const unsigned char \
a1[] = { CHAOS_PP_SEQ_ENUMERATE(l) }, \
a2[] = { CHAOS_PP_SEQ_ENUMERATE(h) }, \
a21[] = { \
CHAOS_PP_SEQ_ENUMERATE( \
CHAOS_PP_EXPR(CHAOS_PP_FOR_EACH_PRODUCT( \
B, \
((CHAOS_PP_SEQ) l) \
((CHAOS_PP_SEQ) h) \
)) \
) \
}; \
/**/
#define B(s, seq) \
(CHAOS_PP_SEQ_ELEM(0, seq) | (CHAOS_PP_SEQ_ELEM(1, seq) << 4)) \
/**/
A((0x1)(0x2), (0x3)(0x4))
#undef A
#undef B
int main() {
for (int i = 0; i != sizeof(a21) / sizeof(unsigned char); ++i) {
printf("0x%x\n", a21[i]);
}
return EXIT_SUCCESS;
}
您可以对每个数组的内容使用 Xmacro。 它与其他答案类似,但不需要额外的库,而且很容易掌握。
#define A(X, ...) \
X(1, __VA_ARGS__) \
X(2, __VA_ARGS__)
#define B(X, ...) \
X(3, __VA_ARGS__) \
X(4, __VA_ARGS__)
#define SIMPLE(VAL,...) 0x ##VAL,
#define JOIN(X,Y,...) 0x ## Y ## X,
#define EXPAND_A(...) A(JOIN, __VA_ARGS__)
const char a1[2] = { A(SIMPLE,) };
const char a2[2] = { B(SIMPLE,) };
const char a21[4] = { B(EXPAND_A,) };
生产:
const char a1[2] = { 0x1, 0x2, };
const char a2[2] = { 0x3, 0x4, };
const char a21[4] = { 0x31, 0x32, 0x41, 0x42, };
a1
和 a2
的初始值设定项是使用 SIMPLE
宏创建的。
笛卡尔积是通过在 B Xmacro 内扩展 A Xmacro 创建的,通过应用 JOIN
最终确定。
宏参数后的额外逗号用于使要求 ...
包含至少一个参数的 C99 兼容编译器静音。
令人惊讶的是,一个空标记是一个有效参数!