mirror of
git://gcc.gnu.org/git/gcc.git
synced 2025-04-08 18:40:53 +08:00
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.
This commit is contained in:
parent
14cfa01755
commit
60d84e8263
@ -324,8 +324,10 @@ static bool nonnull_check_p (tree, unsigned HOST_WIDE_INT);
|
||||
if they match the mask.
|
||||
|
||||
Masks for languages:
|
||||
C --std=c89: D_C99 | D_CXXONLY | D_OBJC | D_CXX_OBJC
|
||||
C --std=c99: D_CXXONLY | D_OBJC
|
||||
C --std=c89: D_C99 | D_C2X | D_CXXONLY | D_OBJC | D_CXX_OBJC
|
||||
C --std=c99: D_C2X | D_CXXONLY | D_OBJC
|
||||
C --std=c17: D_C2X | D_CXXONLY | D_OBJC
|
||||
C --std=c2x: D_CXXONLY | D_OBJC
|
||||
ObjC is like C except that D_OBJC and D_CXX_OBJC are not set
|
||||
C++ --std=c++98: D_CONLY | D_CXX11 | D_CXX20 | D_OBJC
|
||||
C++ --std=c++11: D_CONLY | D_CXX20 | D_OBJC
|
||||
@ -500,7 +502,7 @@ const struct c_common_resword c_common_reswords[] =
|
||||
{ "namespace", RID_NAMESPACE, D_CXXONLY | D_CXXWARN },
|
||||
{ "new", RID_NEW, D_CXXONLY | D_CXXWARN },
|
||||
{ "noexcept", RID_NOEXCEPT, D_CXXONLY | D_CXX11 | D_CXXWARN },
|
||||
{ "nullptr", RID_NULLPTR, D_CXXONLY | D_CXX11 | D_CXXWARN },
|
||||
{ "nullptr", RID_NULLPTR, D_C2X | D_CXX11 | D_CXXWARN },
|
||||
{ "operator", RID_OPERATOR, D_CXXONLY | D_CXXWARN },
|
||||
{ "private", RID_PRIVATE, D_CXX_OBJC | D_CXXWARN },
|
||||
{ "protected", RID_PROTECTED, D_CXX_OBJC | D_CXXWARN },
|
||||
@ -4723,6 +4725,18 @@ c_common_nodes_and_builtins (void)
|
||||
null_node = make_int_cst (1, 1);
|
||||
TREE_TYPE (null_node) = c_common_type_for_size (POINTER_SIZE, 0);
|
||||
|
||||
/* Create the built-in nullptr node. This part of its initialization is
|
||||
common to C and C++. The front ends can further adjust its definition
|
||||
in {c,cxx}_init_decl_processing. In particular, we aren't setting the
|
||||
alignment here for C++ backward ABI bug compatibility. */
|
||||
nullptr_type_node = make_node (NULLPTR_TYPE);
|
||||
TYPE_SIZE (nullptr_type_node) = bitsize_int (GET_MODE_BITSIZE (ptr_mode));
|
||||
TYPE_SIZE_UNIT (nullptr_type_node) = size_int (GET_MODE_SIZE (ptr_mode));
|
||||
TYPE_UNSIGNED (nullptr_type_node) = 1;
|
||||
TYPE_PRECISION (nullptr_type_node) = GET_MODE_BITSIZE (ptr_mode);
|
||||
SET_TYPE_MODE (nullptr_type_node, ptr_mode);
|
||||
nullptr_node = build_int_cst (nullptr_type_node, 0);
|
||||
|
||||
/* Since builtin_types isn't gc'ed, don't export these nodes. */
|
||||
memset (builtin_types, 0, sizeof (builtin_types));
|
||||
}
|
||||
|
@ -375,6 +375,8 @@ enum c_tree_index
|
||||
CTI_DEFAULT_FUNCTION_TYPE,
|
||||
|
||||
CTI_NULL,
|
||||
CTI_NULLPTR,
|
||||
CTI_NULLPTR_TYPE,
|
||||
|
||||
/* These are not types, but we have to look them up all the time. */
|
||||
CTI_FUNCTION_NAME_DECL,
|
||||
@ -409,7 +411,7 @@ struct c_common_resword
|
||||
{
|
||||
const char *const word;
|
||||
ENUM_BITFIELD(rid) const rid : 16;
|
||||
const unsigned int disable : 16;
|
||||
const unsigned int disable : 32;
|
||||
};
|
||||
|
||||
/* Mode used to build pointers (VOIDmode means ptr_mode). */
|
||||
@ -447,19 +449,20 @@ extern machine_mode c_default_pointer_mode;
|
||||
#define D_CONLY 0x0001 /* C only (not in C++). */
|
||||
#define D_CXXONLY 0x0002 /* C++ only (not in C). */
|
||||
#define D_C99 0x0004 /* In C, C99 only. */
|
||||
#define D_CXX11 0x0008 /* In C++, C++11 only. */
|
||||
#define D_EXT 0x0010 /* GCC extension. */
|
||||
#define D_EXT89 0x0020 /* GCC extension incorporated in C99. */
|
||||
#define D_ASM 0x0040 /* Disabled by -fno-asm. */
|
||||
#define D_OBJC 0x0080 /* In Objective C and neither C nor C++. */
|
||||
#define D_CXX_OBJC 0x0100 /* In Objective C, and C++, but not C. */
|
||||
#define D_CXXWARN 0x0200 /* In C warn with -Wcxx-compat. */
|
||||
#define D_CXX_CONCEPTS 0x0400 /* In C++, only with concepts. */
|
||||
#define D_TRANSMEM 0X0800 /* C++ transactional memory TS. */
|
||||
#define D_CXX_CHAR8_T 0X1000 /* In C++, only with -fchar8_t. */
|
||||
#define D_CXX20 0x2000 /* In C++, C++20 only. */
|
||||
#define D_CXX_COROUTINES 0x4000 /* In C++, only with coroutines. */
|
||||
#define D_CXX_MODULES 0x8000 /* In C++, only with modules. */
|
||||
#define D_C2X 0x0008 /* In C, C2X only. */
|
||||
#define D_CXX11 0x0010 /* In C++, C++11 only. */
|
||||
#define D_EXT 0x0020 /* GCC extension. */
|
||||
#define D_EXT89 0x0040 /* GCC extension incorporated in C99. */
|
||||
#define D_ASM 0x0080 /* Disabled by -fno-asm. */
|
||||
#define D_OBJC 0x0100 /* In Objective C and neither C nor C++. */
|
||||
#define D_CXX_OBJC 0x0200 /* In Objective C, and C++, but not C. */
|
||||
#define D_CXXWARN 0x0400 /* In C warn with -Wcxx-compat. */
|
||||
#define D_CXX_CONCEPTS 0x0800 /* In C++, only with concepts. */
|
||||
#define D_TRANSMEM 0x1000 /* C++ transactional memory TS. */
|
||||
#define D_CXX_CHAR8_T 0x2000 /* In C++, only with -fchar8_t. */
|
||||
#define D_CXX20 0x4000 /* In C++, C++20 only. */
|
||||
#define D_CXX_COROUTINES 0x8000 /* In C++, only with coroutines. */
|
||||
#define D_CXX_MODULES 0x10000 /* In C++, only with modules. */
|
||||
|
||||
#define D_CXX_CONCEPTS_FLAGS D_CXXONLY | D_CXX_CONCEPTS
|
||||
#define D_CXX_CHAR8_T_FLAGS D_CXXONLY | D_CXX_CHAR8_T
|
||||
@ -534,6 +537,9 @@ extern const unsigned int num_c_common_reswords;
|
||||
|
||||
/* The node for C++ `__null'. */
|
||||
#define null_node c_global_trees[CTI_NULL]
|
||||
/* The nodes for `nullptr'. */
|
||||
#define nullptr_node c_global_trees[CTI_NULLPTR]
|
||||
#define nullptr_type_node c_global_trees[CTI_NULLPTR_TYPE]
|
||||
|
||||
extern GTY(()) tree c_global_trees[CTI_MAX];
|
||||
|
||||
@ -1009,6 +1015,9 @@ extern void c_parse_final_cleanups (void);
|
||||
#define DECL_UNNAMED_BIT_FIELD(NODE) \
|
||||
(DECL_C_BIT_FIELD (NODE) && !DECL_NAME (NODE))
|
||||
|
||||
/* True iff TYPE is cv decltype(nullptr). */
|
||||
#define NULLPTR_TYPE_P(TYPE) (TREE_CODE (TYPE) == NULLPTR_TYPE)
|
||||
|
||||
extern tree do_case (location_t, tree, tree);
|
||||
extern tree build_stmt (location_t, enum tree_code, ...);
|
||||
extern tree build_real_imag_expr (location_t, enum tree_code, tree);
|
||||
|
@ -321,6 +321,7 @@ pp_c_pointer (c_pretty_printer *pp, tree t)
|
||||
_Bool -- C99
|
||||
_Complex -- C99
|
||||
_Imaginary -- C99
|
||||
nullptr_t -- C23
|
||||
struct-or-union-specifier
|
||||
enum-specifier
|
||||
typedef-name.
|
||||
@ -424,6 +425,9 @@ c_pretty_printer::simple_type_specifier (tree t)
|
||||
else
|
||||
translate_string ("<anonymous>");
|
||||
break;
|
||||
case NULLPTR_TYPE:
|
||||
pp_c_ws_string (this, "nullptr_t");
|
||||
break;
|
||||
|
||||
default:
|
||||
pp_unsupported_tree (this, t);
|
||||
@ -678,6 +682,7 @@ c_pretty_printer::direct_abstract_declarator (tree t)
|
||||
case COMPLEX_TYPE:
|
||||
case TYPE_DECL:
|
||||
case ERROR_MARK:
|
||||
case NULLPTR_TYPE:
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -1219,6 +1224,8 @@ c_pretty_printer::constant (tree e)
|
||||
pp_c_character_constant (this, e);
|
||||
else if (TREE_CODE (type) == ENUMERAL_TYPE)
|
||||
pp_c_enumeration_constant (this, e);
|
||||
else if (NULLPTR_TYPE_P (type))
|
||||
pp_string (this, "nullptr");
|
||||
else
|
||||
pp_c_integer_constant (this, e);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 ();
|
||||
|
@ -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
|
||||
|
@ -187,9 +187,6 @@ enum cp_tree_index
|
||||
CPTI_NOEXCEPT_FALSE_SPEC,
|
||||
CPTI_NOEXCEPT_DEFERRED_SPEC,
|
||||
|
||||
CPTI_NULLPTR,
|
||||
CPTI_NULLPTR_TYPE,
|
||||
|
||||
CPTI_ANY_TARG,
|
||||
|
||||
CPTI_MODULE_HWM,
|
||||
@ -254,8 +251,6 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
|
||||
#define conv_op_marker cp_global_trees[CPTI_CONV_OP_MARKER]
|
||||
#define abort_fndecl cp_global_trees[CPTI_ABORT_FNDECL]
|
||||
#define current_aggr cp_global_trees[CPTI_AGGR_TAG]
|
||||
#define nullptr_node cp_global_trees[CPTI_NULLPTR]
|
||||
#define nullptr_type_node cp_global_trees[CPTI_NULLPTR_TYPE]
|
||||
/* std::align_val_t */
|
||||
#define align_type_node cp_global_trees[CPTI_ALIGN_TYPE]
|
||||
|
||||
@ -4405,9 +4400,6 @@ get_vec_init_expr (tree t)
|
||||
|| TREE_CODE (TYPE) == REAL_TYPE \
|
||||
|| TREE_CODE (TYPE) == COMPLEX_TYPE)
|
||||
|
||||
/* True iff TYPE is cv decltype(nullptr). */
|
||||
#define NULLPTR_TYPE_P(TYPE) (TREE_CODE (TYPE) == NULLPTR_TYPE)
|
||||
|
||||
/* [basic.types]
|
||||
|
||||
Arithmetic types, enumeration types, pointer types,
|
||||
|
@ -4793,16 +4793,10 @@ cxx_init_decl_processing (void)
|
||||
}
|
||||
}
|
||||
|
||||
nullptr_type_node = make_node (NULLPTR_TYPE);
|
||||
TYPE_SIZE (nullptr_type_node) = bitsize_int (GET_MODE_BITSIZE (ptr_mode));
|
||||
TYPE_SIZE_UNIT (nullptr_type_node) = size_int (GET_MODE_SIZE (ptr_mode));
|
||||
TYPE_UNSIGNED (nullptr_type_node) = 1;
|
||||
TYPE_PRECISION (nullptr_type_node) = GET_MODE_BITSIZE (ptr_mode);
|
||||
/* C++-specific nullptr initialization. */
|
||||
if (abi_version_at_least (9))
|
||||
SET_TYPE_ALIGN (nullptr_type_node, GET_MODE_ALIGNMENT (ptr_mode));
|
||||
SET_TYPE_MODE (nullptr_type_node, ptr_mode);
|
||||
record_builtin_type (RID_MAX, "decltype(nullptr)", nullptr_type_node);
|
||||
nullptr_node = build_int_cst (nullptr_type_node, 0);
|
||||
}
|
||||
|
||||
if (! supports_one_only ())
|
||||
|
@ -443,6 +443,14 @@ typedef struct {
|
||||
#endif
|
||||
#endif /* C++11. */
|
||||
|
||||
#if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L)
|
||||
#ifndef _GCC_NULLPTR_T
|
||||
#define _GCC_NULLPTR_T
|
||||
typedef __typeof__(nullptr) nullptr_t;
|
||||
/* ??? This doesn't define __STDC_VERSION_STDDEF_H__ yet. */
|
||||
#endif
|
||||
#endif /* C23. */
|
||||
|
||||
#endif /* _STDDEF_H was defined this time */
|
||||
|
||||
#endif /* !_STDDEF_H && !_STDDEF_H_ && !_ANSI_STDDEF_H && !__STDDEF_H__
|
||||
|
10
gcc/testsuite/gcc.dg/c11-nullptr-1.c
Normal file
10
gcc/testsuite/gcc.dg/c11-nullptr-1.c
Normal file
@ -0,0 +1,10 @@
|
||||
/* Test that in pre-C23 modes, nullptr is a normal identifier,
|
||||
not a keyword. */
|
||||
/* { dg-options "-std=c11 -pedantic-errors" } */
|
||||
|
||||
int nullptr;
|
||||
|
||||
void
|
||||
f (int nullptr)
|
||||
{
|
||||
}
|
10
gcc/testsuite/gcc.dg/c17-nullptr-1.c
Normal file
10
gcc/testsuite/gcc.dg/c17-nullptr-1.c
Normal file
@ -0,0 +1,10 @@
|
||||
/* Test that in pre-C23 modes, nullptr is a normal identifier,
|
||||
not a keyword. */
|
||||
/* { dg-options "-std=c17 -pedantic-errors" } */
|
||||
|
||||
int nullptr;
|
||||
|
||||
void
|
||||
f (int nullptr)
|
||||
{
|
||||
}
|
10
gcc/testsuite/gcc.dg/c17-nullptr-2.c
Normal file
10
gcc/testsuite/gcc.dg/c17-nullptr-2.c
Normal file
@ -0,0 +1,10 @@
|
||||
/* Test that we don't predefine `nullptr' pre-C2X. */
|
||||
/* { dg-do compile } */
|
||||
/* { dg-options "-std=c17 -pedantic-errors" } */
|
||||
|
||||
int *
|
||||
fn (int *p)
|
||||
{
|
||||
p = nullptr; /* { dg-error "'nullptr' undeclared" } */
|
||||
return p;
|
||||
}
|
298
gcc/testsuite/gcc.dg/c2x-nullptr-1.c
Normal file
298
gcc/testsuite/gcc.dg/c2x-nullptr-1.c
Normal file
@ -0,0 +1,298 @@
|
||||
/* Test valid usage of C23 nullptr. */
|
||||
/* { dg-do run } */
|
||||
/* { dg-options "-std=c2x -pedantic-errors -Wall -Wextra -Wno-unused-variable" } */
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
typedef __typeof__(nullptr) nullptr_t;
|
||||
|
||||
void f1 (nullptr_t) { }
|
||||
void f2 (int *) { }
|
||||
void f3 (_Bool) { }
|
||||
nullptr_t cmp (void) { return nullptr; }
|
||||
|
||||
/* The type nullptr_t shall not be converted to any type other than void, bool or
|
||||
a pointer type. No type other than nullptr_t shall be converted to nullptr_t. */
|
||||
void
|
||||
test1 (void)
|
||||
{
|
||||
const nullptr_t nptr = nullptr;
|
||||
static nullptr_t static_nptr;
|
||||
int *p1 = nullptr;
|
||||
void *p2 = nullptr;
|
||||
float *p3 = nullptr;
|
||||
void (*p4)(int) = nullptr;
|
||||
int (*p5)[10] = nullptr;
|
||||
int *p6 = nptr;
|
||||
void *p7 = nptr;
|
||||
float *p8 = nptr;
|
||||
void (*p9)(int) = nptr;
|
||||
int (*p10)[10] = nptr;
|
||||
int *p11 = (int *) nullptr;
|
||||
int *p12 = (int *) nptr;
|
||||
int *p13 = (nullptr);
|
||||
int *p14 = _Generic(0, int : nullptr);
|
||||
if (nullptr || p1 || p2 || p3 || p4 || p5 || p6 || p7 || p8 || p9 || p10
|
||||
|| p11 || p12 || p13 || p14)
|
||||
__builtin_abort ();
|
||||
|
||||
_Bool b1 = nullptr;
|
||||
_Bool b2 = (_Bool) nullptr;
|
||||
_Bool b3 = nptr;
|
||||
_Bool b4 = (_Bool) nptr;
|
||||
_Bool b5 = _Generic(0, int : nullptr);
|
||||
if (b1 || b2 || b3 || b4 || b5 || (_Bool) nullptr || (_Bool) nptr)
|
||||
__builtin_abort ();
|
||||
|
||||
__auto_type a1 = nullptr;
|
||||
__auto_type a2 = nptr;
|
||||
|
||||
/* We can convert nullptr_t to nullptr_t. */
|
||||
__typeof__(nullptr) x = nullptr;
|
||||
f1 (x);
|
||||
f1 (nullptr);
|
||||
f1 (_Generic(0, int : nullptr));
|
||||
f2 (x);
|
||||
f2 (nullptr);
|
||||
f3 (nullptr);
|
||||
|
||||
const nullptr_t np1 = nullptr;
|
||||
const nullptr_t np2 = np1;
|
||||
(void) nullptr;
|
||||
(void) np1;
|
||||
(void) np2;
|
||||
(void) cmp ();
|
||||
(void)(nullptr_t) nullptr;
|
||||
}
|
||||
|
||||
/* Test valid comparison. */
|
||||
void
|
||||
test2 (int *p)
|
||||
{
|
||||
/* If both operands have type nullptr_t or one operand has type nullptr_t
|
||||
and the other is a null pointer constant, they compare equal. */
|
||||
const nullptr_t nptr = nullptr;
|
||||
int r = 0;
|
||||
|
||||
/* Both operands have type nullptr_t. */
|
||||
r |= nullptr != nullptr;
|
||||
r |= cmp () != nullptr;
|
||||
r |= nullptr != cmp ();
|
||||
r |= !(nullptr == nullptr);
|
||||
r |= !(cmp () == nullptr);
|
||||
r |= !(nullptr == cmp ());
|
||||
r |= nptr != nptr;
|
||||
r |= cmp () != nptr;
|
||||
r |= nptr != cmp ();
|
||||
r |= !(nptr == nptr);
|
||||
r |= !(cmp () == nptr);
|
||||
r |= !(nptr == cmp ());
|
||||
|
||||
/* One operand has type nullptr_t and the other is a null pointer constant. */
|
||||
r |= nullptr != (void *) 0;
|
||||
r |= _Generic(0, int : nullptr) != (void *) 0;
|
||||
r |= (nullptr) != (void *) 0;
|
||||
r |= !(nullptr == (void *) 0);
|
||||
r |= (void *) 0 != nullptr;
|
||||
r |= (void *) 0 != (nullptr);
|
||||
r |= !((void *) 0 == nullptr);
|
||||
r |= nullptr != 0;
|
||||
r |= _Generic(0, int : nullptr) != 0;
|
||||
r |= (nullptr) != 0;
|
||||
r |= 0 != nullptr;
|
||||
r |= 0 != (nullptr);
|
||||
r |= !(nullptr == 0);
|
||||
r |= !(0 == nullptr);
|
||||
r |= nullptr != 0u;
|
||||
r |= 0u != nullptr;
|
||||
r |= !(nullptr == 0u);
|
||||
r |= !(0u == nullptr);
|
||||
r |= nptr != (void *) 0;
|
||||
r |= !(nptr == (void *) 0);
|
||||
r |= (void *) 0 != nptr;
|
||||
r |= !((void *) 0 == nptr);
|
||||
r |= nptr != 0;
|
||||
r |= 0 != nptr;
|
||||
r |= !(nptr == 0);
|
||||
r |= !(0 == nptr);
|
||||
r |= nptr != 0u;
|
||||
r |= 0u != nptr;
|
||||
r |= !(nptr == 0u);
|
||||
r |= !(0u == nptr);
|
||||
r |= nptr != _Generic(0, int : nullptr);
|
||||
r |= _Generic(0, int : nullptr) != nptr;
|
||||
if (r)
|
||||
__builtin_abort ();
|
||||
|
||||
/* One operand is a pointer and the other is a null pointer constant. */
|
||||
(void) (p == nullptr);
|
||||
(void) (p != nullptr);
|
||||
(void) (nullptr == p);
|
||||
(void) (nullptr != p);
|
||||
(void) (p == (nullptr));
|
||||
(void) (p != (nullptr));
|
||||
(void) ((nullptr) == p);
|
||||
(void) ((nullptr) != p);
|
||||
(void) ((void *)nullptr == nullptr);
|
||||
(void) ((void *)nullptr != nullptr);
|
||||
(void) (nullptr == (void *)nullptr);
|
||||
(void) (nullptr != (void *)nullptr);
|
||||
(void) (p == _Generic(0, int : nullptr));
|
||||
(void) (p != _Generic(0, int : nullptr));
|
||||
(void) (_Generic(0, int : nullptr) == p);
|
||||
(void) (_Generic(0, int : nullptr) != p);
|
||||
}
|
||||
|
||||
/* Test ?:. */
|
||||
void
|
||||
test3 (int *p, _Bool b)
|
||||
{
|
||||
int x = nullptr ? 1 : 2;
|
||||
(void) x;
|
||||
const nullptr_t nptr = nullptr;
|
||||
/* One of the following shall hold for the second and third operands:
|
||||
-- both operands have nullptr_t type. */
|
||||
__auto_type r1 = b ? nullptr : nullptr;
|
||||
__auto_type r2 = b ? nptr : nptr;
|
||||
/* -- one operand is a pointer and the other is a null pointer constant
|
||||
or has type nullptr_t; */
|
||||
__auto_type r3 = b ? p : nullptr;
|
||||
__auto_type r4 = b ? nullptr : p;
|
||||
__auto_type r5 = b ? nptr : p;
|
||||
__auto_type r6 = b ? p : nptr;
|
||||
__auto_type r7 = b ? 0 : p;
|
||||
__auto_type r8 = b ? p : 0;
|
||||
__auto_type r9 = b ? p : cmp ();
|
||||
__auto_type r10 = b ? cmp () : p;
|
||||
__auto_type r11 = b ? p : _Generic(0, int : nullptr);
|
||||
__auto_type r12 = b ? _Generic(0, int : nullptr) : p;
|
||||
}
|
||||
|
||||
void test_arg1 (const nullptr_t, _Atomic nullptr_t, volatile nullptr_t) { }
|
||||
void test_arg2 (_Atomic int *, const int *, volatile int *) { }
|
||||
void test_arg3 (_Atomic _Bool, const _Bool, volatile _Bool) { }
|
||||
nullptr_t retn (void) { return nullptr; }
|
||||
_Atomic int *ai (void) { return nullptr; }
|
||||
const int *ci (void) { return nullptr; }
|
||||
volatile int *vi (void) { return nullptr; }
|
||||
_Bool retb (void) { return nullptr; }
|
||||
|
||||
/* Simple assignment. */
|
||||
void
|
||||
test4 (void)
|
||||
{
|
||||
/* -- the left operand has an atomic, qualified, or unqualified version of
|
||||
the nullptr_t type and the type of the right is nullptr_t; */
|
||||
nullptr_t n1;
|
||||
const nullptr_t n2 = nullptr;
|
||||
_Atomic nullptr_t n3 = nullptr;
|
||||
volatile nullptr_t n4 = nullptr;
|
||||
_Atomic volatile nullptr_t n5 = nullptr;
|
||||
n1 = nullptr;
|
||||
n3 = nullptr;
|
||||
n4 = nullptr;
|
||||
n5 = nullptr;
|
||||
n5 = _Generic(0, int : nullptr);
|
||||
/* -- the left operand is an atomic, qualified, or unqualified pointer,
|
||||
and the type of the right is nullptr_t; */
|
||||
int *p1 = cmp ();
|
||||
_Atomic int *p2 = cmp ();
|
||||
const int *volatile p3 = cmp ();
|
||||
const int *const *const p4 = cmp ();
|
||||
double (*const p5)(void) = n1;
|
||||
p2 = _Generic(0, int : nullptr);
|
||||
p3 = nullptr;
|
||||
/* -- the left operand is an atomic, qualified, or unqualified bool, and
|
||||
the type of the right is nullptr_t; */
|
||||
_Bool b1;
|
||||
b1 = cmp ();
|
||||
const _Bool b2 = nullptr;
|
||||
_Atomic _Bool b3;
|
||||
b3 = n1;
|
||||
(void) b1;
|
||||
(void) b3;
|
||||
(void) n3;
|
||||
(void) n4;
|
||||
(void) n5;
|
||||
(void) p2;
|
||||
(void) p3;
|
||||
|
||||
test_arg1 (nullptr, nullptr, nullptr);
|
||||
test_arg2 (nullptr, nullptr, nullptr);
|
||||
test_arg3 (nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
/* var_arg etc. */
|
||||
static void
|
||||
test5 (int i, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start (ap, i);
|
||||
if (va_arg (ap, void *))
|
||||
__builtin_abort ();
|
||||
}
|
||||
|
||||
/* Operand of alignas, sizeof or typeof operators. */
|
||||
void
|
||||
test6 (void)
|
||||
{
|
||||
_Static_assert (sizeof (nullptr) == sizeof (void *), "sizeof (nullptr)");
|
||||
_Static_assert (sizeof (nullptr_t) == sizeof (void *), "sizeof (nullptr_t)");
|
||||
_Static_assert (sizeof (nullptr) == sizeof (char *), "sizeof (nullptr)");
|
||||
_Static_assert (sizeof (nullptr_t) == sizeof (char *), "sizeof (nullptr_t)");
|
||||
_Static_assert (_Alignof (nullptr_t) == _Alignof (char *), "_Alignof (nullptr_t)");
|
||||
__typeof__(nullptr) t = nullptr;
|
||||
f1 (t);
|
||||
_Alignas (nullptr_t) char i1 = 'q';
|
||||
|
||||
_Static_assert (_Generic (nullptr, nullptr_t: 1, default: 0) == 1, "_Generic");
|
||||
_Static_assert (_Generic (t, nullptr_t: 1, default: 0) == 1, "_Generic");
|
||||
_Static_assert (_Generic (cmp (), nullptr_t: 1, default: 0) == 1, "_Generic");
|
||||
_Static_assert (_Generic (0, nullptr_t: 1, int: 2, default: 0) == 2, "_Generic");
|
||||
_Static_assert (_Generic ((void *)0, nullptr_t: 1, void *: 2, default: 0) == 2, "_Generic");
|
||||
_Static_assert (_Generic (nullptr, nullptr_t: 1, void *: 2, default: 0) == 1, "_Generic");
|
||||
}
|
||||
|
||||
/* Play with !, ||, &&. */
|
||||
void
|
||||
test7 (void)
|
||||
{
|
||||
if (nullptr)
|
||||
__builtin_abort ();
|
||||
if (1 && nullptr)
|
||||
__builtin_abort ();
|
||||
if (0 || nullptr)
|
||||
__builtin_abort ();
|
||||
if (nullptr && 1)
|
||||
__builtin_abort ();
|
||||
if (nullptr || 0)
|
||||
__builtin_abort ();
|
||||
if (!nullptr)
|
||||
{
|
||||
}
|
||||
else
|
||||
__builtin_abort ();
|
||||
while (nullptr)
|
||||
__builtin_abort ();
|
||||
int i = 0;
|
||||
do
|
||||
++i;
|
||||
while (nullptr);
|
||||
if (i != 1)
|
||||
__builtin_abort ();
|
||||
for (;nullptr;)
|
||||
__builtin_abort ();
|
||||
}
|
||||
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
int i = 42;
|
||||
test1 ();
|
||||
test2 (&i);
|
||||
test3 (&i, 0);
|
||||
test4 ();
|
||||
test5 (42, nullptr);
|
||||
test6 ();
|
||||
test7 ();
|
||||
}
|
9
gcc/testsuite/gcc.dg/c2x-nullptr-2.c
Normal file
9
gcc/testsuite/gcc.dg/c2x-nullptr-2.c
Normal file
@ -0,0 +1,9 @@
|
||||
/* Test nullptr_t from <stddef.h>. */
|
||||
/* { dg-do compile } */
|
||||
/* { dg-options "-std=c2x -pedantic-errors" } */
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
void f(nullptr_t);
|
||||
_Static_assert (sizeof (nullptr_t) == sizeof (char *), "sizeof (nullptr_t)");
|
||||
_Static_assert (_Alignof (nullptr_t) == _Alignof (char *), "_Alignof (nullptr_t)");
|
80
gcc/testsuite/gcc.dg/c2x-nullptr-3.c
Normal file
80
gcc/testsuite/gcc.dg/c2x-nullptr-3.c
Normal file
@ -0,0 +1,80 @@
|
||||
/* Test wrong usage of C23 nullptr. */
|
||||
/* { dg-do compile } */
|
||||
/* { dg-options "-std=c2x -pedantic-errors -Wall -Wextra -Wno-unused-variable" } */
|
||||
|
||||
typedef __typeof__(nullptr) nullptr_t;
|
||||
|
||||
void g (nullptr_t); /* { dg-message "expected .nullptr_t. but argument is of type .int." } */
|
||||
nullptr_t cmp (void);
|
||||
|
||||
void
|
||||
test1 (int *p)
|
||||
{
|
||||
(void) (p > nullptr); /* { dg-error "ordered comparison" } */
|
||||
(void) (p >= nullptr); /* { dg-error "ordered comparison" } */
|
||||
(void) (p < nullptr); /* { dg-error "ordered comparison" } */
|
||||
(void) (p <= nullptr); /* { dg-error "ordered comparison" } */
|
||||
(void) (nullptr == 1); /* { dg-error "invalid operands" } */
|
||||
(void) (1 == nullptr); /* { dg-error "invalid operands" } */
|
||||
(void) (nullptr != 1); /* { dg-error "invalid operands" } */
|
||||
(void) (1 != nullptr); /* { dg-error "invalid operands" } */
|
||||
(void) (1 > nullptr); /* { dg-error "invalid operands" } */
|
||||
|
||||
/* "(nullptr_t)nullptr" has type nullptr_t but isn't an NPC. */
|
||||
(void) ((nullptr_t)nullptr == p); /* { dg-error "invalid operands" } */
|
||||
(void) ((nullptr_t)nullptr != p); /* { dg-error "invalid operands" } */
|
||||
(void) (p == (nullptr_t)nullptr); /* { dg-error "invalid operands" } */
|
||||
(void) (p != (nullptr_t)nullptr); /* { dg-error "invalid operands" } */
|
||||
(void) (cmp () == p); /* { dg-error "invalid operands" } */
|
||||
(void) (cmp () != p); /* { dg-error "invalid operands" } */
|
||||
(void) (p == cmp ()); /* { dg-error "invalid operands" } */
|
||||
(void) (p != cmp ()); /* { dg-error "invalid operands" } */
|
||||
/* "(void *)nullptr" is not an NPC, either. */
|
||||
(void) ((void *)nullptr == cmp ()); /* { dg-error "invalid operands" } */
|
||||
(void) ((void *)nullptr != cmp ()); /* { dg-error "invalid operands" } */
|
||||
(void) (cmp () == (void *)nullptr); /* { dg-error "invalid operands" } */
|
||||
(void) (cmp () != (void *)nullptr); /* { dg-error "invalid operands" } */
|
||||
}
|
||||
|
||||
void
|
||||
test2 (void)
|
||||
{
|
||||
const nullptr_t nptr = nullptr;
|
||||
int p = nullptr; /* { dg-error "incompatible types" } */
|
||||
float d = nullptr; /* { dg-error "incompatible types" } */
|
||||
char arr[10] = { nullptr }; /* { dg-error "incompatible types" } */
|
||||
|
||||
/* No type other than nullptr_t shall be converted to nullptr_t. */
|
||||
const nullptr_t n = 0; /* { dg-error "invalid initializer" } */
|
||||
+(nullptr_t) 0; /* { dg-error "conversion from .int. to .nullptr_t." } */
|
||||
|
||||
g (0); /* { dg-error "incompatible type" } */
|
||||
|
||||
int i = 42 + nullptr; /* { dg-error "invalid operands" } */
|
||||
|
||||
/* The assignment of an object of type nullptr_t with a value of another
|
||||
type, even if the value is a null pointer constant, is a constraint
|
||||
violation. */
|
||||
nullptr_t m;
|
||||
m = 0; /* { dg-error "incompatible types" } */
|
||||
(void) m;
|
||||
nullptr_t o = 0; /* { dg-error "invalid initializer" } */
|
||||
|
||||
switch (nullptr); /* { dg-error "switch quantity not an integer" } */
|
||||
}
|
||||
|
||||
/* If a second or third operand of type nullptr_t is used that is not a null
|
||||
pointer constant and the other operand is not a pointer or does not have
|
||||
itself nullptr_t, a constraint is violated even if that other operand is
|
||||
a null pointer constant such as 0. */
|
||||
void
|
||||
test3 (_Bool b, int i)
|
||||
{
|
||||
const nullptr_t nptr = nullptr;
|
||||
__auto_type a1 = b ? nptr : i; /* { dg-error "type mismatch" } */
|
||||
__auto_type a2 = b ? i : nptr; /* { dg-error "type mismatch" } */
|
||||
__auto_type a3 = b ? nptr : 0; /* { dg-error "type mismatch" } */
|
||||
__auto_type a4 = b ? 0 : nptr; /* { dg-error "type mismatch" } */
|
||||
__auto_type a5 = b ? 0 : nullptr; /* { dg-error "type mismatch" } */
|
||||
__auto_type a6 = b ? nullptr : 0; /* { dg-error "type mismatch" } */
|
||||
}
|
11
gcc/testsuite/gcc.dg/c2x-nullptr-4.c
Normal file
11
gcc/testsuite/gcc.dg/c2x-nullptr-4.c
Normal file
@ -0,0 +1,11 @@
|
||||
/* Test that -Wc11-c2x-compat issues a warning (not a pedwarn) about
|
||||
`nullptr' in C2X. */
|
||||
/* { dg-do compile } */
|
||||
/* { dg-options "-std=c2x -pedantic-errors -Wc11-c2x-compat" } */
|
||||
|
||||
int *
|
||||
fn (int *p)
|
||||
{
|
||||
p = nullptr; /* { dg-warning "ISO C does not support .nullptr. before C2X" } */
|
||||
return p;
|
||||
}
|
14
gcc/testsuite/gcc.dg/c2x-nullptr-5.c
Normal file
14
gcc/testsuite/gcc.dg/c2x-nullptr-5.c
Normal file
@ -0,0 +1,14 @@
|
||||
/* Test that we don't lose side-effects when converting from nullptr_t. */
|
||||
/* { dg-do run } */
|
||||
/* { dg-options "-std=c2x -pedantic-errors" } */
|
||||
|
||||
int i;
|
||||
nullptr_t fn () { ++i; return nullptr; }
|
||||
|
||||
int
|
||||
main ()
|
||||
{
|
||||
int *p = fn ();
|
||||
if (i != 1)
|
||||
__builtin_abort ();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user