我可以确定参数是否是字符串文字?

时间:2011-04-17 03:26:35

标签: c++ c

是否可以确定在宏或函数中传递的参数在编译时或运行时是否是字符串文字?

例如,

#define is_string_literal(X)
...
...   

is_string_literal("hello") == true;
const char * p = "hello";
is_string_literal(p) == false;

bool is_string_literal(const char * s);

is_string_literal("hello") == true;
const char * p = "hello";
is_string_literal(p) == false;

感谢。

9 个答案:

答案 0 :(得分:25)

YES! (感谢James McNellisGMan进行更正。更新以正确处理像"Hello, " "World!"这样的连接文字,这些文字在连接之前会被字符串化。)

#define is_literal_(x) is_literal_f(#x, sizeof(#x) - 1)
#define is_literal(x) is_literal_(x)

bool is_literal_f(const char *s, size_t l)
{
    const char *e = s + l;
    if(s[0] == 'L') s++;
    if(s[0] != '"') return false;
    for(; s != e; s = strchr(s + 1, '"'))
      {
        if(s == NULL) return false;
        s++;
        while(isspace(*s)) s++;
        if(*s != '"') return false;
      }
    return true;
}

这将在将参数传递给函数之前对其进行字符串化,因此如果参数是字符串文字,则传递给我们函数的参数将被引号字符包围。

如果你认为这是一个字符串文字:

const char *p = "string";
// should is_literal(p) be true or false?

我帮不了你。您可以使用某些实现定义(或* shudder * undefined)行为来测试字符串是否存储在只读内存中,但是可以修改某些(可能是较旧的)系统p

对于那些质疑使用这种功能的人,请考虑:

enum string_type { LITERAL, ARRAY, POINTER };

void string_func(/*const? */char *c, enum string_type t);

而不是在每次调用时显式指定string_function的第二个参数,is_literal允许我们用宏包装它:

#define string_func(s) \
    (string_func)(s, is_literal(s)  ? LITERAL :
        (void *)s == (void *)&s ? ARRAY : POINTER)

我无法想象为什么它会产生影响,除了在普通的C中,文字不是const并且由于某种原因你不想/不能把这个函数写成一个{ {1}}代替const char *。但是有各种各样的理由要做某事。有一天,你也可能觉得需要采取可怕的黑客行为。

答案 1 :(得分:5)

通过以下技术了解编译时(如上所述)。您可以确定给定参数是否为字符串文字。如果它是像const char x[], *p那样的数组或指针;那么它会抛出编译器错误。

#define is_string_literal(X) _is_string_literal("" X)
bool _is_string_literal (const char *str) { return true; } // practically not needed

[注意:我之前的回答是由专家投票决定的,并且在编辑后仍未被接受或投票。我正在回答相同的内容。]

答案 2 :(得分:3)

没有。字符串文字只是char(在C中)或const char(在C ++中)的数组。

你无法区分字符串文字和char之类的其他数组(在C ++中):

const char x[] = "Hello, World!";

答案 3 :(得分:1)

试试这个:

#define is_string_literal(s) \
  (memcmp(#s, "\"", 1) == 0)

根据C / C ++变量命名约定,变量名必须以'_'或字母开头。

答案 4 :(得分:1)

如果expr是字符串文字,则clang和gcc的__builtin_constant_p(expr)返回1,否则返回0。用clang 3.5+和gcc 4.6+进行了测试。

由于clang预先定义了 GCC ,因此您可以简单地

#ifdef __GCC__
  ... use __builtin_constant_p(expr)
#else
  ... use fallback
#endif

请参见https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html

答案 5 :(得分:0)

我有一个类似的问题:我想说那个

MY_MACRO("compile-time string")

合法,那

char buffer[200]="a string";
MY_MACRO(buffer)

是合法的,但不允许

MY_MACRO(szArbitraryDynamicString)

我使用了GCC的__builtin_types_compatible_p和MSVC的_countof,它们似乎以拒绝短字符串文字为代价正常工作。

答案 6 :(得分:0)

由于c ++中的字符串文字可以具有不同的前缀,因此没有必要检查起始引号:https://en.cppreference.com/w/cpp/language/string_literal

最好检查结尾的报价:

  • C ++ 11
  • msvc2015u3,gcc5.4,clang3.8.0

    #include <type_traits>
    
    #define UTILITY_CONST_EXPR_VALUE(exp)                   ::utility::const_expr_value<decltype(exp), exp>::value
    
    // hint: operator* applies to character literals, but not to double-quoted literals
    #define UTILITY_LITERAL_CHAR_(c_str, char_type)         UTILITY_CONST_EXPR_VALUE(((void)(c_str * 0), ::utility::literal_char_caster<typename ::utility::remove_cvref<char_type>::type>::cast_from(c_str, L ## c_str, u ## c_str, U ## c_str)))
    #define UTILITY_LITERAL_CHAR(c_str, char_type)          UTILITY_LITERAL_CHAR_(c_str, char_type)
    
    #define UTILITY_IS_LITERAL_STRING(c_str)                UTILITY_CONST_EXPR_VALUE((sizeof(#c_str) > 1) ? #c_str [sizeof(#c_str) - 2] == UTILITY_LITERAL_CHAR('\"', decltype(c_str[0])) : false)
    
    #define UTILITY_IS_LITERAL_STRING_A(c_str)                   UTILITY_CONST_EXPR_VALUE((sizeof(#c_str) > 1) ? #c_str [sizeof(#c_str) - 2] == '\"' : false)
    #define UTILITY_IS_LITERAL_STRING_WITH_PREFIX(c_str, prefix) UTILITY_CONST_EXPR_VALUE((sizeof(#c_str) > 1) ? #c_str [sizeof(#c_str) - 2] == prefix ## '\"' : false)
    
    namespace utility {
    
        template <typename T, T v>
        struct const_expr_value
        {
            static constexpr const T value = v;
        };
    
        // remove_reference + remove_cv
        template <typename T>
        struct remove_cvref
        {
            using type = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
        };
    
        //// literal_char_caster, literal_string_caster
    
        // template class to replace partial function specialization and avoid overload over different return types
        template <typename CharT>
        struct literal_char_caster;
    
        template <>
        struct literal_char_caster<char>
        {
            static inline constexpr char
                cast_from(
                    char ach,
                    wchar_t wch,
                    char16_t char16ch,
                    char32_t char32ch)
            {
                return ach;
            }
        };
    
        template <>
        struct literal_char_caster<wchar_t>
        {
            static inline constexpr wchar_t
                cast_from(
                    char ach,
                    wchar_t wch,
                    char16_t char16ch,
                    char32_t char32ch)
            {
                return wch;
            }
        };
    
        template <>
        struct literal_char_caster<char16_t>
        {
            static inline constexpr char16_t
                cast_from(
                    char ach,
                    wchar_t wch,
                    char16_t char16ch,
                    char32_t char32ch)
            {
                return char16ch;
            }
        };
    
        template <>
        struct literal_char_caster<char32_t>
        {
            static inline constexpr char32_t
                cast_from(
                    char ach,
                    wchar_t wch,
                    char16_t char16ch,
                    char32_t char32ch)
            {
                return char32ch;
            }
        };
    
    }
    
    const char * a = "123";
    const char b[] = "345";
    
    int main()
    {
        static_assert(UTILITY_IS_LITERAL_STRING_A(a) == 0, "Aa");
        static_assert(UTILITY_IS_LITERAL_STRING(a) == 0, "a");
        static_assert(UTILITY_IS_LITERAL_STRING_A(b) == 0, "Ab");
        static_assert(UTILITY_IS_LITERAL_STRING(b) == 0, "b");
        static_assert(UTILITY_IS_LITERAL_STRING_A("123") == 1, "A123");
        static_assert(UTILITY_IS_LITERAL_STRING_WITH_PREFIX(L"123", L) == 1, "L123");
        static_assert(UTILITY_IS_LITERAL_STRING_WITH_PREFIX(u"123", u) == 1, "u123");
        static_assert(UTILITY_IS_LITERAL_STRING_WITH_PREFIX(U"123", U) == 1, "U123");
        static_assert(UTILITY_IS_LITERAL_STRING("123") == 1, "123");
        static_assert(UTILITY_IS_LITERAL_STRING(L"123") == 1, "L123");
        static_assert(UTILITY_IS_LITERAL_STRING(u"123") == 1, "u123");
        static_assert(UTILITY_IS_LITERAL_STRING(U"123") == 1, "U123");
    }
    

https://godbolt.org/z/UXIRY6

需要UTILITY_CONST_EXPR_VALUE宏来强制编译器生成仅编译时代码。

答案 7 :(得分:0)

也许不是操作者想要的东西,但我使用

#define literal(a) "" s
#define strcpy(a, b ) _strcpy(a, literal(b))

这为

char buf[32];
char buf2[32];
const char *p = "hello,";

strcpy(buf, "hello"); // legal
strcpy,(buf, p);  // illegal
strcpy(buf, buf2); // illegal

答案 8 :(得分:0)

这是C和C ++的一种简单的可移植方法,在编译时进行了完全评估,可测试简单的字符串文字,即:不带反斜杠的单个字符串文字:

#define STR(s)  #s
#define is_string_literal(s)  (sizeof(STR(#s)) == sizeof(#s) + 4 \
                               && #s[0] == '"' && #s[sizeof(#s)-2] == '"')

它只是测试字符串转换以"开始和结束,并且两次应用字符串转换只会将字符串的长度增加4,仅转义开头和结尾的"。 / p>

这是一个测试程序:

#include <stdio.h>

#define STR(s)  #s
#define is_simple_string(s)  (sizeof(STR(#s)) == sizeof(#s) + 4 \
                              && #s[0] == '"' && #s[sizeof(#s)-2] == '"')

int main() {
#define TEST(s)  printf("%16s -> %d\n", #s, is_simple_string(s))
    char buf[4] = "abc";
    const char cbuf[4] = "def";
    char *p = buf;
    const char *cp = cbuf;

#define S "abc"
    TEST(1);
    TEST('x');
    TEST(1.0);
    TEST(1LL);
    TEST(main);
    TEST(main());
    TEST(S);
    TEST("");
    TEST("abc");
    TEST("abc\n");
    TEST("abc\"def");
    TEST("abc" "");
    TEST("abc"[0]);
    TEST("abc"+1);
    TEST("abc"-*"def");
    TEST(""+*"");
    TEST("a"+*"");
    TEST("ab"+*"");
    TEST("abc"+*"");
    TEST(1+"abc");
    TEST(buf);
    TEST(buf + 1);
    TEST(cbuf);
    TEST(cbuf + 1);
    TEST(p);
    TEST(cp);
    TEST(p + 1);
    TEST(cp + 1);
    TEST(&p);
    TEST(&cp);
    TEST(&buf);
    TEST(&cbuf);

    return *cp - *p - 3;
}

它仅输出1TEST(S)TEST("")的{​​{1}}。