实现编译时机制检查字符串的唯一性

Posted

技术标签:

【中文标题】实现编译时机制检查字符串的唯一性【英文标题】:Implementing compile-time mechanism checking uniqueness of a string 【发布时间】:2014-08-20 19:10:59 【问题描述】:

定义我的问题的最简单方法是我试图实现一种机制来检查是否已经使用了相同的字符串(或一对(数字,字符串))。我希望使用 C 预处理器以一种智能的方式实现这种机制。我还希望这种机制在调试模式下(通过检查断言)出现冲突或运行时错误时给我编译错误。我们不希望开发人员在添加消息时出错,因为每条消息都应该是唯一的。我知道这可以通过计算散列或例如 crc/md5 来完成,但这种机制很容易发生冲突,我需要避免。至关重要的是,每条消息只能使用一次。

此机制的示例行为:

addMessage(1, "Message1") //OK 
addMessage(2, "Message2") //OK 
. 
. 
. 
addMessage(N, "MessageN") //OK 
addMessage(2, "Message2") //Compile error, Message2 has already been used 

替代行为(调试代码时):

addMessage(1, "Message1") //OK 
addMessage(2, "Message2") //OK 
. 
. 
. 
addMessage(N, "MessageN") //OK 
addMessage(2, "Message2") //Assertion failed, because Message2 has already been used 

首选的方法是巧妙地使用 #define#undef 指令。一般来说,预处理器应该以一种聪明的方式使用(我不确定这是否可能)也许它可以通过适当的宏组合来实现?任何可以帮助我解决这个问题的 C 预处理器黑客?

//编辑:我需要这些消息在全球范围内是唯一的,而不仅仅是在一个代码块内(如 if 语句的函数)。

//EDIT2:对问题的最佳描述是我有 100 个不同的源文件,我想用预处理器(或可能在编译开始时使用脚本解析源文件以外的其他机制进行检查)如果字符串(或预处理器定义)被多次使用,这将非常耗时,并且会为足够复杂的项目添加另一个阶段)。我仍然不知道该怎么做(我知道这可能根本不可能,但我希望确实如此)。

【问题讨论】:

你会在什么情况下使用addMessage()?在函数内,类型定义,初始化,全局... 一般来说,它会在一个类中本地使用,但是它也可以全局(通过宏)添加到全局范围(如果它有助于解决这个问题)。范围没有限制,有需要可以在全球范围内使用。 我明白了。如果你可以在任何地方使用它,那么它比你只能在一个特定的头文件中全局使用它要复杂得多。 我可以在头文件中全局使用它,但它如何帮助我在编译步骤或调试二进制文件中检查字符串的重复。 是否可以选择将消息存储在 C/C++ 源文件以外的其他文件中,并编写一个脚本来读取该文件、验证您的要求并生成一个 C/C++ 文件但是您需要定义的消息?此 C/C++ 文件将被视为构建产品,不会签入您的 VCS。 【参考方案1】:

这将给出重复字符串的错误:

constexpr bool isequal(char const *one, char const *two) 
  return (*one && *two) ? (*one == *two && isequal(one + 1, two + 1))
    : (!*one && !*two);


constexpr bool isunique(const char *test, const char* const* list)

    return *list == 0 || !isequal(test, *list) && isunique(test, list + 1);


constexpr int no_duplicates(const char* const* list, int idx)

    return *list == 0 ? -1 : (isunique(*list, list + 1) ? no_duplicates(list + 1, idx + 1) : idx);


template <int V1, int V2> struct assert_equality

    static const char not_equal_warning = V1 + V2 + 1000;
;

template <int V> struct assert_equality<V, V>

    static const bool not_equal_warning = 0;
;

constexpr const char* l[] = "aa", "bb", "aa", 0;
static_assert(assert_equality<no_duplicates(l, 0), -1>::not_equal_warning == 0, "duplicates found");

g++ 的输出:

g++ -std=c++11 unique.cpp 
unique.cpp: In instantiation of ‘const char assert_equality<0, -1>::not_equal_warning’:
unique.cpp:29:57:   required from here
unique.cpp:20:53: warning: overflow in implicit constant conversion [-Woverflow]
unique.cpp:29:1: error: static assertion failed: duplicates found

“assert_equality”的第一个模板参数(在本例中为 0)告诉您重复字符串的第一个位置。

【讨论】:

【参考方案2】:

我不确定使用标准 C++ 预处理器是否可以轻松实现(我猜它不是)。您可能会使用其他一些预处理器(例如GPP)

你可以用另一种方式:从其他来源生成一些X-macro“头”文件(使用例如一个小的awk脚本,它可以验证唯一性)。然后自定义您的构建(例如,向您的Makefile 添加一些规则)以运行该生成脚本以生成头文件。

或者,如果您坚持在编译器内部完成处理,并且如果您的编译器是最近的 GCC,请考虑使用 MELT 自定义 GCC(例如,通过添加适当的内置函数或编译指示来完成这项工作)。

在上个世纪,我在emacs 编辑器中破解了一个小的 Emacs 函数来完成类似的工作(对错误消息进行唯一编号)(在保存 C 文件之前重新编号一些 #define-s)。

【讨论】:

【参考方案3】:

我假设这样的事情会起作用:

addMessage(1, "Message1")
addMessage(2, "Message1")

或者:

addMessage(1, "Message") /* transforms into "Message_1" */
addMessage(2, "Message_1") /* transforms into "Message_1_2" */

由于 C 预处理器延迟扩展标记并禁止在另一个宏中定义宏,因此无法保存执行一个宏的结果以便另一个宏可以使用它。

另一方面,强制符号的唯一性绝对是可能的:

#define addMessage(N, MSG) const char *_error_message_##N (void)  return MSG; 

或者:

#define addMessage(N, MSG) const char *_error_message_##N (void)  return MSG "_" #N; 

因为在链接步骤中,名称为_error_message_NUMBER 的重复符号将触发错误。而且因为它是一个函数,所以它不能在另一个函数内部使用而不触发错误。

【讨论】:

它只能在一个块内工作(例如在一个函数或一个 if 块中)。我希望能够在整个程序中拥有唯一标识符,无论调用发生在哪里。【参考方案4】:

假设您的编译器仍然不兼容 C++11,因为您没有适当地标记。我还假设您并不特别关注错误消息,只是您希望它工作。在这种情况下,以下基于宏的解决方案可能适合您

#include <iostream>
#include <string>
#define ADD_MESSAGE(N, MSG) \
char * MSG;                   \
addMessage(N, #MSG); 


void addMessage(int n, std::string msg)
    
    std::cout << msg << std::endl;
    

int main() 
    ADD_MESSAGE(1, Message1); //OK 
    ADD_MESSAGE(2, Message2); //OK 
    ADD_MESSAGE(3, MessageN); //OK 
    ADD_MESSAGE(4, Message2); //Compile error, Message2 has already been used 
    ;

编译输出

prog.cpp: In function ‘int main()’:
prog.cpp:17:17: error: redeclaration of ‘char* Message2’
  ADD_MESSAGE(4, Message2); //Compile error, Message2 has already been used 
                 ^
prog.cpp:4:8: note: in definition of macro ‘ADD_MESSAGE’
 char * MSG;                   \
        ^
prog.cpp:15:17: error: ‘char* Message2’ previously declared here
  ADD_MESSAGE(2, Message2); //OK 
                 ^
prog.cpp:4:8: note: in definition of macro ‘ADD_MESSAGE’
 char * MSG;                   \
        ^

【讨论】:

我不确定可以在项目中使用的标准的实际版本,但据我所知,我们没有使用 C++11。明天我会检查你的解决方案,如果它对我有用,然后给你反馈。 addMessage(N, "MSG"); --> addMessage(N, #MSG); 对于多字错误消息、任何带有 C 关键字的错误消息或任何不能编译为有效 C 标识符的错误消息都会失败。【参考方案5】:

如果你不关心大量无用的样板,那么这里有一个完全是预处理器的样板,所以不用担心范围,然后在程序启动时检查它们是否是唯一的。 p>

在文件中:

#ifndef ERROR1
#define ERROR1 "1"
#endif
#ifndef ERROR2
#define ERROR2 "2"
#endif
...
#ifndef ERROR255
#define ERROR255 "255"
#endif

#include <assert.h>
#include <set>
#include <string>

class CheckUnique 
    CheckUnique() 
        std::set<std::string> s;
        static const char *messages = 
#if HAVE_BOOST
# include <boost/preprocessor.hpp>
# define BOOST_PP_LOCAL_LIMITS (1, 254)
# define BOOST_PP_LOCAL_MACRO(N) ERROR ## N,
# include BOOST_PP_LOCAL_ITERATE()
#else // HAVE_BOOST
             ERROR1,
             ERROR2,
             ...
#endif // HAVE_BOOST
             ERROR255
        ;
        for (int i = 0; i < sizeof messages / sizeof *messages; i++) 
            if (s.count(messages[i]))
                assert(! "I found two error messages that were the same");
            else
                s.insert(messages[i]);
        
     
 ;

 static CheckUnique check;

这个文件可以是#included 在每个源文件的末尾,或者你可以把它放到一个自己的文件中,并包括每个有#define ERROR 行的文件。这样一来,操作系统一加载程序,check的构造函数就会运行并抛出异常。

这还要求您可以访问 Boost.Preprocessor 库(它只是头文件,因此很容易设置)。虽然如果你不能使用它,那么你可以硬编码错误宏,就像我在 #if HAVE_BOOST 块中展示的那样。

这里的大部分样板都非常简单,因此如果您使用程序(如某种可移植脚本)生成它,那么它会让您的生活更轻松,但仍然可以一次性完成。

【讨论】:

我不能在项目中使用 boost。

以上是关于实现编译时机制检查字符串的唯一性的主要内容,如果未能解决你的问题,请参考以下文章

带有编译时格式字符串检查的自定义 fmt 格式化函数

fmt 库:如何使用 RegEx 添加编译时字符串检查?

mysql使用触发器生成唯一订单号,

在 Kotlin Android 中将值传递给函数时进行编译时间检查

runtime的意义

java中的反射怎么用c实现