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

时间:2014-08-20 19:10:59

标签: c++ c c-preprocessor

定义我的问题最简单的方法是,我正在尝试实现一种机制来检查是否已经使用了相同的字符串(或一对(数字,字符串))。我希望使用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个不同的源文件,我想检查预处理器(或者除了在编译开始时使用脚本解析源文件之外的其他机制)如果一个字符串(或预处理器定义)被多次使用,那么每次都会非常耗时并且会为一个足够复杂的项目添加另一个阶段。我仍然不知道该怎么做(我知道它可能根本不可能,但我希望它实际上是这样)。

5 个答案:

答案 0 :(得分:4)

这会在重复的字符串上出错:

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

第一个模板参数(在本例中为0)到&#39; assert_equality&#39;告诉你重复字符串的第一个位置。

答案 1 :(得分:2)

我不确定使用标准C ++预处理器很容易实现(我猜它不是)。您可以使用其他预处理器(例如GPP

你可以采用另一种方式:生成一些X-macro&#34;标题&#34;来自其他来源的文件(使用例如一个小的awk脚本,这将验证单一性)。然后自定义您的构建(例如,向Makefile添加一些规则)以运行该生成脚本以生成头文件。

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

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

答案 2 :(得分:2)

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

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的重复符号将触发错误。并且因为它是一个函数,所以它不能在不触发错误的情况下在另一个函数内使用。

答案 3 :(得分:2)

假设您的编译器仍然不符合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;                   \
        ^

答案 4 :(得分:1)

如果你不关心大量无用的锅炉板,那么这里就完全是预处理器,所以不用担心范围,然后检查它们在程序启动。

在档案中:

#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;

此文件可以是#include d,位于每个源文件的末尾,或者您可以将其放入自己的文件中,并包含其中包含#define ERROR行的每个文件。这样,只要操作系统加载程序,检查的构造函数就会运行并抛出异常。

这也要求您可以访问Boost.Preprocessor库(仅限它的标题,因此它很容易设置)。虽然如果你不能使用它,那么你可以像我在#if HAVE_BOOST块中显示的那样对错误宏进行硬编码。

这里的大部分锅炉板非常简单,所以如果你用一个程序(比如某种便携式脚本)生成它,那么它会让你的生活变得更加容易,但它仍然可以一次完成。< / p>