aboutsummaryrefslogtreecommitdiff
path: root/gcc/c
diff options
context:
space:
mode:
authorMarek Polacek <polacek@redhat.com>2022-08-10 15:00:49 -0400
committerMarek Polacek <polacek@redhat.com>2022-08-25 17:54:13 -0400
commit60d84e82639e25b025a926b19ec5a92fba4447ce (patch)
tree7bbbeeb4ff4c16132b02c74f95a936d80cd09779 /gcc/c
parent14cfa01755a66afbae2539f8b5796c960ddcecc6 (diff)
downloadgcc-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.cc25
-rw-r--r--gcc/c/c-decl.cc6
-rw-r--r--gcc/c/c-parser.cc10
-rw-r--r--gcc/c/c-typeck.cc57
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