diff options
author | Marek Polacek <polacek@redhat.com> | 2022-08-10 15:00:49 -0400 |
---|---|---|
committer | Marek Polacek <polacek@redhat.com> | 2022-08-25 17:54:13 -0400 |
commit | 60d84e82639e25b025a926b19ec5a92fba4447ce (patch) | |
tree | 7bbbeeb4ff4c16132b02c74f95a936d80cd09779 /gcc/c | |
parent | 14cfa01755a66afbae2539f8b5796c960ddcecc6 (diff) | |
download | gcc-60d84e82639e25b025a926b19ec5a92fba4447ce.zip gcc-60d84e82639e25b025a926b19ec5a92fba4447ce.tar.gz gcc-60d84e82639e25b025a926b19ec5a92fba4447ce.tar.bz2 |
c: Implement C23 nullptr (N3042)
This patch implements the C23 nullptr literal:
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3042.htm> (with
wording fixes from N3047), which is intended to replace the problematic
definition of NULL which might be either of integer type or void*.
Since C++ has had nullptr for over a decade now, it was relatively easy
to just move the built-in node definitions from the C++ FE to the C/C++
common code. Also, our DWARF emitter already handles NULLPTR_TYPE by
emitting DW_TAG_unspecified_type. However, I had to handle a lot of
contexts such as ?:, comparison, conversion, etc.
There are some minor differences, e.g. in C you can do
bool b = nullptr;
but in C++ you have to use direct-initialization:
bool b{nullptr};
And I think that
nullptr_t n = 0;
is only valid in C++.
Of course, C doesn't have to handle mangling, RTTI, substitution,
overloading, ...
This patch also defines nullptr_t in <stddef.h>. However, it does not
define __STDC_VERSION_STDDEF_H__ yet, because we don't know yet what value
it should be defined to.
gcc/c-family/ChangeLog:
* c-common.cc (c_common_reswords): Enable nullptr in C2X.
(c_common_nodes_and_builtins): Create the built-in node for nullptr.
* c-common.h (enum c_tree_index): Add CTI_NULLPTR, CTI_NULLPTR_TYPE.
(struct c_common_resword): Resize the disable member.
(D_C2X): Add.
(nullptr_node): Define.
(nullptr_type_node): Define.
(NULLPTR_TYPE_P): Define.
* c-pretty-print.cc (c_pretty_printer::simple_type_specifier): Handle
NULLPTR_TYPE.
(c_pretty_printer::direct_abstract_declarator): Likewise.
(c_pretty_printer::constant): Likewise.
gcc/c/ChangeLog:
* c-convert.cc (c_convert) <case POINTER_TYPE>: Handle NULLPTR_TYPE.
Give a better diagnostic when converting to nullptr_t.
* c-decl.cc (c_init_decl_processing): Perform C-specific nullptr
initialization.
* c-parser.cc (c_parse_init): Maybe OR D_C2X into mask.
(c_parser_postfix_expression): Handle RID_NULLPTR.
* c-typeck.cc (null_pointer_constant_p): Return true when expr is
nullptr_node.
(build_unary_op) <case TRUTH_NOT_EXPR>: Handle NULLPTR_TYPE.
(build_conditional_expr): Handle the case when the second/third operand
is NULLPTR_TYPE and third/second operand is POINTER_TYPE.
(convert_for_assignment): Handle converting an expression of type
nullptr_t to pointer/bool.
(build_binary_op) <case TRUTH_XOR_EXPR>: Handle NULLPTR_TYPE.
<case EQ_EXPR>: Handle comparing operands of type nullptr_t.
gcc/cp/ChangeLog:
* cp-tree.h (enum cp_tree_index): Remove CTI_NULLPTR, CTI_NULLPTR_TYPE.
Move it to c_tree_index.
(nullptr_node): No longer define here.
(nullptr_type_node): Likewise.
(NULLPTR_TYPE_P): Likewise.
* decl.cc (cxx_init_decl_processing): Only keep C++-specific nullptr
initialization; move the shared code to c_common_nodes_and_builtins.
gcc/ChangeLog:
* ginclude/stddef.h: Define nullptr_t.
gcc/testsuite/ChangeLog:
* gcc.dg/c11-nullptr-1.c: New test.
* gcc.dg/c17-nullptr-1.c: New test.
* gcc.dg/c17-nullptr-2.c: New test.
* gcc.dg/c2x-nullptr-1.c: New test.
* gcc.dg/c2x-nullptr-2.c: New test.
* gcc.dg/c2x-nullptr-3.c: New test.
* gcc.dg/c2x-nullptr-4.c: New test.
* gcc.dg/c2x-nullptr-5.c: New test.
Diffstat (limited to 'gcc/c')
-rw-r--r-- | gcc/c/c-convert.cc | 25 | ||||
-rw-r--r-- | gcc/c/c-decl.cc | 6 | ||||
-rw-r--r-- | gcc/c/c-parser.cc | 10 | ||||
-rw-r--r-- | gcc/c/c-typeck.cc | 57 |
4 files changed, 88 insertions, 10 deletions
diff --git a/gcc/c/c-convert.cc b/gcc/c/c-convert.cc index 18083d5..6e74913 100644 --- a/gcc/c/c-convert.cc +++ b/gcc/c/c-convert.cc @@ -133,6 +133,20 @@ c_convert (tree type, tree expr, bool init_const) (loc, type, c_objc_common_truthvalue_conversion (input_location, expr)); case POINTER_TYPE: + /* The type nullptr_t may be converted to a pointer type. The result is + a null pointer value. */ + if (NULLPTR_TYPE_P (TREE_TYPE (e))) + { + /* To make sure that (void *)nullptr is not a null pointer constant, + build_c_cast will create an additional NOP_EXPR around the result + of this conversion. */ + if (TREE_SIDE_EFFECTS (e)) + ret = build2 (COMPOUND_EXPR, type, e, build_int_cst (type, 0)); + else + ret = build_int_cst (type, 0); + goto maybe_fold; + } + gcc_fallthrough (); case REFERENCE_TYPE: ret = convert_to_pointer (type, e); goto maybe_fold; @@ -180,7 +194,16 @@ c_convert (tree type, tree expr, bool init_const) return ret; } - error ("conversion to non-scalar type requested"); + /* If we are converting to nullptr_t, don't say "non-scalar type" because + the nullptr_t type is a scalar type. Only nullptr_t shall be converted + to nullptr_t. */ + if (code == NULLPTR_TYPE) + { + error ("conversion from %qT to %qT", TREE_TYPE (e), type); + inform (input_location, "only %qT can be converted to %qT", type, type); + } + else + error ("conversion to non-scalar type requested"); return error_mark_node; } diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc index 1fe31e0..804314d 100644 --- a/gcc/c/c-decl.cc +++ b/gcc/c/c-decl.cc @@ -4531,6 +4531,12 @@ c_init_decl_processing (void) pushdecl (build_decl (UNKNOWN_LOCATION, TYPE_DECL, get_identifier ("_Bool"), boolean_type_node)); + /* C-specific nullptr initialization. */ + record_builtin_type (RID_MAX, "nullptr_t", nullptr_type_node); + /* The size and alignment of nullptr_t is the same as for a pointer to + character type. */ + SET_TYPE_ALIGN (nullptr_type_node, GET_MODE_ALIGNMENT (ptr_mode)); + input_location = save_loc; make_fname_decl = c_make_fname_decl; diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc index 1e8d9dc..0fe2ff5 100644 --- a/gcc/c/c-parser.cc +++ b/gcc/c/c-parser.cc @@ -119,6 +119,8 @@ c_parse_init (void) mask |= D_CXXONLY; if (!flag_isoc99) mask |= D_C99; + if (!flag_isoc2x) + mask |= D_C2X; if (flag_no_asm) { mask |= D_ASM | D_EXT; @@ -10253,6 +10255,14 @@ c_parser_postfix_expression (c_parser *parser) "%<depend%> clause"); expr.set_error (); break; + /* C23 'nullptr' literal. */ + case RID_NULLPTR: + c_parser_consume_token (parser); + expr.value = nullptr_node; + set_c_expr_source_range (&expr, tok_range); + pedwarn_c11 (loc, OPT_Wpedantic, + "ISO C does not support %qs before C2X", "nullptr"); + break; default: c_parser_error (parser, "expected expression"); expr.set_error (); diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc index 0e37ab8..f3d83d1 100644 --- a/gcc/c/c-typeck.cc +++ b/gcc/c/c-typeck.cc @@ -133,6 +133,13 @@ null_pointer_constant_p (const_tree expr) /* This should really operate on c_expr structures, but they aren't yet available everywhere required. */ tree type = TREE_TYPE (expr); + + /* An integer constant expression with the value 0, such an expression + cast to type void*, or the predefined constant nullptr, are a null + pointer constant. */ + if (expr == nullptr_node) + return true; + return (TREE_CODE (expr) == INTEGER_CST && !TREE_OVERFLOW (expr) && integer_zerop (expr) @@ -4575,7 +4582,7 @@ build_unary_op (location_t location, enum tree_code code, tree xarg, case TRUTH_NOT_EXPR: if (typecode != INTEGER_TYPE && typecode != FIXED_POINT_TYPE && typecode != REAL_TYPE && typecode != POINTER_TYPE - && typecode != COMPLEX_TYPE) + && typecode != COMPLEX_TYPE && typecode != NULLPTR_TYPE) { error_at (location, "wrong type argument to unary exclamation mark"); @@ -5515,6 +5522,13 @@ build_conditional_expr (location_t colon_loc, tree ifexp, bool ifexp_bcp, } result_type = type2; } + /* 6.5.15: "if one is a null pointer constant (other than a pointer) or has + type nullptr_t and the other is a pointer, the result type is the pointer + type." */ + else if (code1 == NULLPTR_TYPE && code2 == POINTER_TYPE) + result_type = type2; + else if (code1 == POINTER_TYPE && code2 == NULLPTR_TYPE) + result_type = type1; if (!result_type) { @@ -7613,12 +7627,13 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type, error_at (location, msg); return error_mark_node; } - else if (codel == POINTER_TYPE && coder == INTEGER_TYPE) + else if (codel == POINTER_TYPE + && (coder == INTEGER_TYPE || coder == NULLPTR_TYPE)) { - /* An explicit constant 0 can convert to a pointer, - or one that results from arithmetic, even including - a cast to integer type. */ - if (!null_pointer_constant) + /* An explicit constant 0 or type nullptr_t can convert to a pointer, + or one that results from arithmetic, even including a cast to + integer type. */ + if (!null_pointer_constant && coder != NULLPTR_TYPE) switch (errtype) { case ic_argpass: @@ -7691,7 +7706,10 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type, return convert (type, rhs); } - else if (codel == BOOLEAN_TYPE && coder == POINTER_TYPE) + else if (codel == BOOLEAN_TYPE + /* The type nullptr_t may be converted to bool. The + result is false. */ + && (coder == POINTER_TYPE || coder == NULLPTR_TYPE)) { tree ret; bool save = in_late_binary_op; @@ -12112,10 +12130,10 @@ build_binary_op (location_t location, enum tree_code code, case TRUTH_XOR_EXPR: if ((code0 == INTEGER_TYPE || code0 == POINTER_TYPE || code0 == REAL_TYPE || code0 == COMPLEX_TYPE - || code0 == FIXED_POINT_TYPE) + || code0 == FIXED_POINT_TYPE || code0 == NULLPTR_TYPE) && (code1 == INTEGER_TYPE || code1 == POINTER_TYPE || code1 == REAL_TYPE || code1 == COMPLEX_TYPE - || code1 == FIXED_POINT_TYPE)) + || code1 == FIXED_POINT_TYPE || code1 == NULLPTR_TYPE)) { /* Result of these operations is always an int, but that does not mean the operands should be @@ -12423,6 +12441,27 @@ build_binary_op (location_t location, enum tree_code code, result_type = type1; pedwarn (location, 0, "comparison between pointer and integer"); } + /* 6.5.9: One of the following shall hold: + -- both operands have type nullptr_t; */ + else if (code0 == NULLPTR_TYPE && code1 == NULLPTR_TYPE) + { + result_type = nullptr_type_node; + /* No need to convert the operands to result_type later. */ + converted = 1; + } + /* -- one operand has type nullptr_t and the other is a null pointer + constant. We will have to convert the former to the type of the + latter, because during gimplification we can't have mismatching + comparison operand type. We convert from nullptr_t to the other + type, since only nullptr_t can be converted to nullptr_t. Also, + even a constant 0 is a null pointer constant, so we may have to + create a pointer type from its type. */ + else if (code0 == NULLPTR_TYPE && null_pointer_constant_p (orig_op1)) + result_type = (INTEGRAL_TYPE_P (type1) + ? build_pointer_type (type1) : type1); + else if (code1 == NULLPTR_TYPE && null_pointer_constant_p (orig_op0)) + result_type = (INTEGRAL_TYPE_P (type0) + ? build_pointer_type (type0) : type0); if ((TREE_CODE (TREE_TYPE (orig_op0)) == BOOLEAN_TYPE || truth_value_p (TREE_CODE (orig_op0))) ^ (TREE_CODE (TREE_TYPE (orig_op1)) == BOOLEAN_TYPE |