Hi,
I updated my patch based on all your comments.
the major changes are the following:
1. replace the candidate calls with __builtin_str(n)cmp_eq instead of
__builtin_memcmp_eq;
in builtins.c, when expanding the new __builtin_str(n)cmp_eq call,
expand them first as
__builtin_memcmp_eq, if Not succeed, change the call back to
__builtin_str(n)cmp.
2. change the call to “get_range_strlen” with “compute_objsize”.
3. add the missing case for equality checking with zero;
4. adjust the new testing case for PR83026; add a new testing case for
the missing case added in 3.
5. update “uhwi” to “shwi” for where it needs;
6. some other minor format changes.
the changes are retested on x86 and aarch64, bootstrapped and regression
tested. no issue.
Okay for trunk?
thanks.
Qing
Please see the updated patch:
gcc/ChangeLog:
+2017-12-21 Qing Zhao <[email protected]>
+
+ PR middle-end/78809
+ PR middle-end/83026
+ * builtins.c (expand_builtin): Add the handling of BUILT_IN_STRCMP_EQ
+ and BUILT_IN_STRNCMP_EQ.
+ * builtins.def: Add new builtins BUILT_IN_STRCMP_EQ and
+ BUILT_IN_STRNCMP_EQ.
+ * tree-ssa-strlen.c (compute_string_length): New function.
+ (handle_builtin_string_cmp): New function to handle calls to
+ string compare functions.
+ (strlen_optimize_stmt): Add handling to builtin string compare
+ calls.
+ * tree.c (build_common_builtin_nodes): Add new defines of
+ BUILT_IN_STRNCMP_EQ and BUILT_IN_STRCMP_EQ.
+
gcc/testsuite/ChangeLog
+2017-12-21 Qing Zhao <[email protected]>
+
+ PR middle-end/78809
+ * gcc.dg/strcmpopt_2.c: New testcase.
+ * gcc.dg/strcmpopt_3.c: New testcase.
+
+ PR middle-end/83026
+ * gcc.dg/strcmpopt_3.c: New testcase.
+
---
gcc/builtins.c | 33 ++++++
gcc/builtins.def | 5 +
gcc/testsuite/gcc.dg/strcmpopt_2.c | 67 ++++++++++++
gcc/testsuite/gcc.dg/strcmpopt_3.c | 31 ++++++
gcc/testsuite/gcc.dg/strcmpopt_4.c | 16 +++
gcc/tree-ssa-strlen.c | 215 +++++++++++++++++++++++++++++++++++++
gcc/tree.c | 8 ++
7 files changed, 375 insertions(+)
create mode 100644 gcc/testsuite/gcc.dg/strcmpopt_2.c
create mode 100644 gcc/testsuite/gcc.dg/strcmpopt_3.c
create mode 100644 gcc/testsuite/gcc.dg/strcmpopt_4.c
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 6b25253..a5f6885 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -6953,12 +6953,45 @@ expand_builtin (tree exp, rtx target, rtx subtarget,
machine_mode mode,
return target;
break;
+ /* expand it as BUILT_IN_MEMCMP_EQ first. If not successful, change it
+ back to a BUILT_IN_STRCMP. Remember to delete the 3rd paramater
+ when changing it to a strcmp call. */
+ case BUILT_IN_STRCMP_EQ:
+ target = expand_builtin_memcmp (exp, target, true);
+ if (target)
+ return target;
+
+ /* change this call back to a BUILT_IN_STRCMP. */
+ TREE_OPERAND (exp, 1)
+ = build_fold_addr_expr (builtin_decl_explicit (BUILT_IN_STRCMP));
+
+ /* delete the last parameter. */
+ unsigned int i;
+ vec<tree, va_gc> *arg_vec;
+ vec_alloc (arg_vec, 2);
+ for (i = 0; i < 2; i++)
+ arg_vec->quick_push (CALL_EXPR_ARG (exp, i));
+ exp = build_call_vec (TREE_TYPE (exp), CALL_EXPR_FN (exp), arg_vec);
+ /* FALLTHROUGH */
+
case BUILT_IN_STRCMP:
target = expand_builtin_strcmp (exp, target);
if (target)
return target;
break;
+ /* expand it as BUILT_IN_MEMCMP_EQ first. If not successful, change it
+ back to a BUILT_IN_STRNCMP. */
+ case BUILT_IN_STRNCMP_EQ:
+ target = expand_builtin_memcmp (exp, target, true);
+ if (target)
+ return target;
+
+ /* change it back to a BUILT_IN_STRNCMP. */
+ TREE_OPERAND (exp, 1)
+ = build_fold_addr_expr (builtin_decl_explicit (BUILT_IN_STRNCMP));
+ /* FALLTHROUGH */
+
case BUILT_IN_STRNCMP:
target = expand_builtin_strncmp (exp, target, mode);
if (target)
diff --git a/gcc/builtins.def b/gcc/builtins.def
index 2281021..9eb79fd 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -949,6 +949,11 @@ DEF_BUILTIN_STUB (BUILT_IN_ALLOCA_WITH_ALIGN_AND_MAX,
"__builtin_alloca_with_ali
equality with zero. */
DEF_BUILTIN_STUB (BUILT_IN_MEMCMP_EQ, "__builtin_memcmp_eq")
+/* An internal version of strcmp/strncmp, used when the result is only
+ tested for equality with zero. */
+DEF_BUILTIN_STUB (BUILT_IN_STRCMP_EQ, "__builtin_strcmp_eq")
+DEF_BUILTIN_STUB (BUILT_IN_STRNCMP_EQ, "__builtin_strncmp_eq")
+
/* Object size checking builtins. */
DEF_GCC_BUILTIN (BUILT_IN_OBJECT_SIZE, "object_size",
BT_FN_SIZE_CONST_PTR_INT, ATTR_PURE_NOTHROW_LEAF_LIST)
DEF_EXT_LIB_BUILTIN_CHKP (BUILT_IN_MEMCPY_CHK, "__memcpy_chk",
BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
diff --git a/gcc/testsuite/gcc.dg/strcmpopt_2.c
b/gcc/testsuite/gcc.dg/strcmpopt_2.c
new file mode 100644
index 0000000..0131b8f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/strcmpopt_2.c
@@ -0,0 +1,67 @@
+/* { dg-do run } */
+/* { dg-options "-O2 -fdump-tree-strlen" } */
+
+char s[100] = {'a','b','c','d'};
+typedef struct { char s[8]; int x; } S;
+
+__attribute__ ((noinline)) int
+f1 (S *s)
+{
+ return __builtin_strcmp (s->s, "abc") != 0;
+}
+
+__attribute__ ((noinline)) int
+f2 (void)
+{
+ return __builtin_strcmp (s, "abc") != 0;
+}
+
+__attribute__ ((noinline)) int
+f3 (S *s)
+{
+ return __builtin_strcmp ("abc", s->s) != 0;
+}
+
+__attribute__ ((noinline)) int
+f4 (void)
+{
+ return __builtin_strcmp ("abc", s) != 0;
+}
+
+__attribute__ ((noinline)) int
+f5 (S *s)
+{
+ return __builtin_strncmp (s->s, "abc", 3) != 0;
+}
+
+__attribute__ ((noinline)) int
+f6 (void)
+{
+ return __builtin_strncmp (s, "abc", 2) != 0;
+}
+
+__attribute__ ((noinline)) int
+f7 (S *s)
+{
+ return __builtin_strncmp ("abc", s->s, 3) != 0;
+}
+
+__attribute__ ((noinline)) int
+f8 (void)
+{
+ return __builtin_strncmp ("abc", s, 2) != 0;
+}
+
+int main (void)
+{
+ S ss = {{'a','b','c'}, 2};
+
+ if (f1 (&ss) != 0 || f2 () != 1 || f3 (&ss) != 0 ||
+ f4 () != 1 || f5 (&ss) != 0 || f6 () != 0 ||
+ f7 (&ss) != 0 || f8 () != 0)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* { dg-final { scan-tree-dump-times "cmp_eq \\(" 8 "strlen" } } */
diff --git a/gcc/testsuite/gcc.dg/strcmpopt_3.c
b/gcc/testsuite/gcc.dg/strcmpopt_3.c
new file mode 100644
index 0000000..86a0d7a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/strcmpopt_3.c
@@ -0,0 +1,31 @@
+/* { dg-do run } */
+/* { dg-options "-O2 -fdump-tree-strlen" } */
+
+__attribute__ ((noinline)) int
+f1 (void)
+{
+ char *s0= "abcd";
+ char s[8];
+ __builtin_strcpy (s, s0);
+ return __builtin_strcmp(s, "abc") != 0;
+}
+
+__attribute__ ((noinline)) int
+f2 (void)
+{
+ char *s0 = "ab";
+ char s[8];
+ __builtin_strcpy (s, s0);
+ return __builtin_strcmp("abc", s) != 0;
+}
+
+int main (void)
+{
+ if (f1 () != 1
+ || f2 () != 1)
+ __builtin_abort ();
+
+ return 0;
+}
+
+/* { dg-final { scan-tree-dump-times "strcmp" 0 "strlen" } } */
diff --git a/gcc/testsuite/gcc.dg/strcmpopt_4.c
b/gcc/testsuite/gcc.dg/strcmpopt_4.c
new file mode 100644
index 0000000..d727bc3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/strcmpopt_4.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-strlen" } */
+
+typedef struct { char s[8]; int x; } S;
+extern int max_i;
+
+int
+f1 (S * s)
+{
+ int result, i;
+ for (i = 0; i < max_i; i++)
+ result += __builtin_strcmp (s->s, "abc") != 0 ? 2 : 1;
+ return result;
+}
+
+/* { dg-final { scan-tree-dump-times "cmp_eq \\(" 1 "strlen" } } */
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
index 94f20ef..57563ef 100644
--- a/gcc/tree-ssa-strlen.c
+++ b/gcc/tree-ssa-strlen.c
@@ -2540,6 +2540,216 @@ handle_builtin_memcmp (gimple_stmt_iterator *gsi)
return false;
}
+/* Given an index to the strinfo vector, compute the string length for the
+ corresponding string. Return -1 when unknown. */
+
+static HOST_WIDE_INT
+compute_string_length (int idx)
+{
+ HOST_WIDE_INT string_leni = -1;
+ gcc_assert (idx != 0);
+
+ if (idx < 0)
+ string_leni = ~idx;
+ else
+ {
+ strinfo *si = get_strinfo (idx);
+ if (si)
+ {
+ tree const_string_len = get_string_length (si);
+ if (const_string_len && tree_fits_shwi_p (const_string_len))
+ string_leni = tree_to_shwi (const_string_len);
+ }
+ }
+
+ if (string_leni < 0)
+ return -1;
+
+ return string_leni;
+}
+
+/* Handle a call to strcmp or strncmp. When the result is ONLY used to do
+ equality test against zero:
+
+ A. When both arguments are constant strings and it's a strcmp:
+ * if the length of the strings are NOT equal, we can safely fold the call
+ to a non-zero value.
+ * otherwise, do nothing now.
+
+ B. When one of the arguments is constant string, try to replace the call
with
+ a __builtin_str(n)cmp_eq call where possible, i.e:
+
+ strncmp (s, STR, C) (!)= 0 in which, s is a pointer to a string, STR is a
+ constant string, C is a constant.
+ if (C <= strlen(STR) && sizeof_array(s) > C)
+ {
+ replace this call with
+ strncmp_eq (s, STR, C) (!)= 0
+ }
+ if (C > strlen(STR)
+ {
+ it can be safely treated as a call to strcmp (s, STR) (!)= 0
+ can handled by the following strcmp.
+ }
+
+ strcmp (s, STR) (!)= 0 in which, s is a pointer to a string, STR is a
+ constant string.
+ if (sizeof_array(s) > strlen(STR))
+ {
+ replace this call with
+ strcmp_eq (s, STR, strlen(STR)+1) (!)= 0
+ }
+ */
+
+static bool
+handle_builtin_string_cmp (gimple_stmt_iterator *gsi)
+{
+ gcall *stmt = as_a <gcall *> (gsi_stmt (*gsi));
+ tree res = gimple_call_lhs (stmt);
+ use_operand_p use_p;
+ imm_use_iterator iter;
+ tree arg1 = gimple_call_arg (stmt, 0);
+ tree arg2 = gimple_call_arg (stmt, 1);
+ int idx1 = get_stridx (arg1);
+ int idx2 = get_stridx (arg2);
+ HOST_WIDE_INT length = -1;
+ bool is_ncmp = false;
+
+ if (!res)
+ return true;
+
+ /* When both arguments are unknown, do nothing. */
+ if (idx1 == 0 && idx2 == 0)
+ return true;
+
+ /* Handle strncmp function. */
+ if (gimple_call_num_args (stmt) == 3)
+ {
+ tree len = gimple_call_arg (stmt, 2);
+ if (tree_fits_shwi_p (len))
+ length = tree_to_shwi (len);
+
+ is_ncmp = true;
+ }
+
+ /* For strncmp, if the length argument is NOT known, do nothing. */
+ if (is_ncmp && length < 0)
+ return true;
+
+ /* When the result is ONLY used to do equality test against zero. */
+ FOR_EACH_IMM_USE_FAST (use_p, iter, res)
+ {
+ gimple *use_stmt = USE_STMT (use_p);
+
+ if (is_gimple_debug (use_stmt))
+ continue;
+ if (gimple_code (use_stmt) == GIMPLE_ASSIGN)
+ {
+ tree_code code = gimple_assign_rhs_code (use_stmt);
+ if (code == COND_EXPR)
+ {
+ tree cond_expr = gimple_assign_rhs1 (use_stmt);
+ if ((TREE_CODE (cond_expr) != EQ_EXPR
+ && (TREE_CODE (cond_expr) != NE_EXPR))
+ || !integer_zerop (TREE_OPERAND (cond_expr, 1)))
+ return true;
+ }
+ else if (code == EQ_EXPR || code == NE_EXPR)
+ {
+ if (!integer_zerop (gimple_assign_rhs2 (use_stmt)))
+ return true;
+ }
+ else
+ return true;
+ }
+ else if (gimple_code (use_stmt) == GIMPLE_COND)
+ {
+ tree_code code = gimple_cond_code (use_stmt);
+ if ((code != EQ_EXPR && code != NE_EXPR)
+ || !integer_zerop (gimple_cond_rhs (use_stmt)))
+ return true;
+ }
+ else
+ return true;
+ }
+
+ /* When both arguments are known, and their strlens are unequal, we can
+ safely fold the call to a non-zero value for strcmp;
+ othewise, do nothing now. */
+ if (idx1 != 0 && idx2 != 0)
+ {
+ HOST_WIDE_INT const_string_leni1 = compute_string_length (idx1);
+ HOST_WIDE_INT const_string_leni2 = compute_string_length (idx2);
+
+ if (!is_ncmp
+ && const_string_leni1 != -1
+ && const_string_leni2 != -1
+ && const_string_leni1 != const_string_leni2)
+ {
+ replace_call_with_value (gsi, integer_one_node);
+ return false;
+ }
+ return true;
+ }
+
+ /* When one of args is constant string. */
+ tree var_string = NULL_TREE;
+ HOST_WIDE_INT const_string_leni = -1;
+
+ if (idx1)
+ {
+ const_string_leni = compute_string_length (idx1);
+ var_string = arg2;
+ }
+ else
+ {
+ gcc_checking_assert (idx2);
+ const_string_leni = compute_string_length (idx2);
+ var_string = arg1;
+ }
+
+ if (const_string_leni < 0)
+ return true;
+
+ unsigned HOST_WIDE_INT var_sizei = 0;
+ /* try to determine the minimum size of the object pointed by var_string. */
+ tree size = compute_objsize (var_string, 2);
+
+ if (!size)
+ return true;
+
+ if (tree_fits_uhwi_p (size))
+ var_sizei = tree_to_uhwi (size);
+
+ if (var_sizei == 0)
+ return true;
+
+ /* For strncmp, if length > const_string_leni , this call can be safely
+ transformed to a strcmp. */
+ if (is_ncmp && length > const_string_leni)
+ is_ncmp = false;
+
+ unsigned HOST_WIDE_INT final_length
+ = is_ncmp ? length : const_string_leni + 1;
+
+ /* Replace strcmp or strncmp with the corresponding str(n)cmp_eq. */
+ if (var_sizei > final_length)
+ {
+ tree fn
+ = (is_ncmp
+ ? builtin_decl_implicit (BUILT_IN_STRNCMP_EQ)
+ : builtin_decl_implicit (BUILT_IN_STRCMP_EQ));
+ if (!fn)
+ return true;
+ tree const_string_len = build_int_cst (size_type_node, final_length);
+ update_gimple_call (gsi, fn, 3, arg1, arg2, const_string_len);
+ }
+ else
+ return true;
+
+ return false;
+}
+
/* Handle a POINTER_PLUS_EXPR statement.
For p = "abcd" + 2; compute associated length, or if
p = q + off is pointing to a '\0' character of a string, call
@@ -2939,6 +3149,11 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
if (!handle_builtin_memcmp (gsi))
return false;
break;
+ case BUILT_IN_STRCMP:
+ case BUILT_IN_STRNCMP:
+ if (!handle_builtin_string_cmp (gsi))
+ return false;
+ break;
default:
break;
}
diff --git a/gcc/tree.c b/gcc/tree.c
index ed1852b..e23b3cd 100644
--- a/gcc/tree.c
+++ b/gcc/tree.c
@@ -10047,6 +10047,14 @@ build_common_builtin_nodes (void)
"__builtin_memcmp_eq",
ECF_PURE | ECF_NOTHROW | ECF_LEAF);
+ local_define_builtin ("__builtin_strncmp_eq", ftype, BUILT_IN_STRNCMP_EQ,
+ "__builtin_strncmp_eq",
+ ECF_PURE | ECF_NOTHROW | ECF_LEAF);
+
+ local_define_builtin ("__builtin_strcmp_eq", ftype, BUILT_IN_STRCMP_EQ,
+ "__builtin_strcmp_eq",
+ ECF_PURE | ECF_NOTHROW | ECF_LEAF);
+
/* If there's a possibility that we might use the ARM EABI, build the
alternate __cxa_end_cleanup node used to resume from C++. */
if (targetm.arm_eabi_unwinder)
--
1.9.1
> On Dec 14, 2017, at 2:45 PM, Jakub Jelinek <[email protected]> wrote:
>
> On Thu, Dec 14, 2017 at 01:45:21PM -0600, Qing Zhao wrote:
>> 2017-12-11 Qing Zhao <[email protected] <mailto:[email protected]>>
>
> No " <mailto:[email protected]>" in ChangeLog entries please.
>
>> --- a/gcc/tree-ssa-strlen.c
>> +++ b/gcc/tree-ssa-strlen.c
>> @@ -2541,6 +2541,198 @@ handle_builtin_memcmp (gimple_stmt_iterator *gsi)
>> return false;
>> }
>>
>> +/* Given an index to the strinfo vector, compute the string length for the
>> + corresponding string. Return -1 when unknown. */
>> +
>> +static HOST_WIDE_INT
>> +compute_string_length (int idx)
>> +{
>> + HOST_WIDE_INT string_leni = -1;
>> + gcc_assert (idx != 0);
>> +
>> + if (idx < 0)
>> + string_leni = ~idx;
>> + else
>> + {
>> + strinfo *si = get_strinfo (idx);
>> + if (si)
>> + {
>> + tree const_string_len = get_string_length (si);
>> + string_leni
>> + = (const_string_len && tree_fits_uhwi_p (const_string_len)
>> + ? tree_to_uhwi(const_string_len) : -1);
>
> So, you are returning a signed HWI, then clearly tree_fits_uhwi_p and
> tree_to_uhwi are inappropriate, you should have used tree_fits_shwi_p
> and tree_to_shwi. Space after function name is missing too.
> And, as you start by initializing string_leni to -1, there is no
> point to write it this way rather than
> if (const_string_len && tree_fits_shwi_p (const_string_len))
> string_leni = tree_to_shwi (const_string_len);
>
>> + }
>> + }
>
> Maybe also do
> if (string_leni < 0)
> return -1;
>
>> + return string_leni;
>
> unless the callers just look for negative value as unusable.
>
>> + tree len = gimple_call_arg (stmt, 2);
>> + if (tree_fits_uhwi_p (len))
>> + length = tree_to_uhwi (len);
>
> Similarly to above, you are mixing signed and unsigned HWIs too much.
>
>> + if (gimple_code (ustmt) == GIMPLE_ASSIGN)
>
> if (is_gimple_assign (ustmt))
>
> Usually we use use_stmt instead of ustmt.
>
>> + {
>> + gassign *asgn = as_a <gassign *> (ustmt);
>
> No need for the gassign and ugly as_a, gimple_assign_rhs_code
> as well as gimple_assign_rhs2 can be called on gimple * too.
>
>> + tree_code code = gimple_assign_rhs_code (asgn);
>> + if ((code != EQ_EXPR && code != NE_EXPR)
>> + || !integer_zerop (gimple_assign_rhs2 (asgn)))
>> + return true;
>> + }
>> + else if (gimple_code (ustmt) == GIMPLE_COND)
>> + {
>> + tree_code code = gimple_cond_code (ustmt);
>> + if ((code != EQ_EXPR && code != NE_EXPR)
>> + || !integer_zerop (gimple_cond_rhs (ustmt)))
>> + return true;
>
> There is another case you are missing, assign stmt with
> gimple_assign_rhs_code COND_EXPR, where gimple_assign_rhs1 is
> tree with TREE_CODE EQ_EXPR or NE_EXPR with TREE_OPERAND (rhs1, 1)
> integer_zerop.
>
>> + /* When both arguments are known, and their strlens are unequal, we can
>> + safely fold the call to a non-zero value for strcmp;
>> + othewise, do nothing now. */
>> + if (idx1 != 0 && idx2 != 0)
>> + {
>> + HOST_WIDE_INT const_string_leni1 = -1;
>> + HOST_WIDE_INT const_string_leni2 = -1;
>> + const_string_leni1 = compute_string_length (idx1);
>> + const_string_leni2 = compute_string_length (idx2);
>
> Why do you initialize the vars when you immediately overwrite it?
> Just do
> HOST_WIDE_INT const_string_leni1 = compute_string_length (idx1);
> etc.
>
>> + /* When one of args is constant string. */
>> + tree var_string;
>> + HOST_WIDE_INT const_string_leni = -1;
>> +
>> + if (idx1)
>> + {
>> + const_string_leni = compute_string_length (idx1);
>> + var_string = arg2;
>> + }
>> + else if (idx2)
>> + {
>> + const_string_leni = compute_string_length (idx2);
>> + var_string = arg1;
>> + }
>
> Haven't you checked earlier that one of idx1 and idx2 is non-zero?
> If so, then the else if (idx2) will just might confuse -Wuninitialized,
> if you just use else, you don't need to initialize const_string_leni
> either.
>
>> + /* Try to get the min and max string length for var_string, the max
>> length is
>> + the size of the array - 1, recorded in size[1]. */
>> + get_range_strlen (var_string, size);
>> + if (size[1] && tree_fits_uhwi_p (size[1]))
>> + var_sizei = tree_to_uhwi (size[1]) + 1;
>
> This is something that looks problematic to me. get_range_strlen returns
> some conservative upper bound on the string length, which is fine if
> var_string points to say a TREE_STATIC variable where you know the allocated
> size, or automatic variable. But if somebody passes you a pointer to a
> structure and the source doesn't contain aggregate copying for it, not sure
> if you can take for granted that all the bytes are readable after the '\0'
> in the string. Hopefully at least for flexible array members and arrays in
> such positions get_range_strlen will not provide the upper bound, but even
> in other cases it doesn't feel safe to me.
>
> Furthermore, in the comments you say that you do it only for small strings,
> but in the patch I can't see any upper bound, so you could transform strlen
> that would happen to return say just 1 or 2 with a function call that
> possibly reads megabytes of data (memcmp may read all bytes, not just stop
> at the first difference).
>> + unsigned HOST_WIDE_INT final_length
>> + = is_ncmp ? length : (const_string_leni + 1);
>
> Why the ()s?
>
> Jakub