Semenalidery.com

IT Новости из мира ПК
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Result pointer nullptr

Что такое nullptr?

446 AraK [2009-08-15 19:47:00]

Теперь у нас есть С++ 11 со многими новыми функциями. Интересным и запутанным (по крайней мере для меня) является новый nullptr .

Ну, больше не нужно для неприятного макроса NULL .

Тем не менее, я не понимаю, как работает nullptr . Например, Статья в Википедии говорит:

С++ 11 исправляет это, введя новое ключевое слово , чтобы служить в качестве выделенной константы нулевого указателя: nullptr. Он имеет тип тип nullptr_t, который неявно конвертируется и сопоставим с любым типом указателя или типом указателя на член. Он не является неявно конвертируемым или сопоставимым с целыми типами, за исключением bool.

Как это ключевое слово и экземпляр типа?

Кроме того, есть ли у вас другой пример (помимо Википедии), где nullptr превосходит старый добрый 0 ?

c++ pointers c++11 nullptr

9 ответов

331 Решение Johannes Schaub — litb [2009-08-15 20:06:00]

Как это ключевое слово и экземпляр типа?

Это не удивительно. Оба true и false являются ключевыми словами, а в качестве литералов у них есть тип ( bool ). nullptr — это литерал-указатель типа std::nullptr_t , и это prvalue (вы не можете взять его адрес с помощью & ).

4.10 о преобразовании указателя говорит, что prvalue типа std::nullptr_t является константой нулевого указателя и что интегральная константа нулевого указателя может быть преобразована в std::nullptr_t . Противоположное направление не допускается. Это позволяет перегрузить функцию как для указателей, так и для целых чисел и передать nullptr для выбора версии указателя. Передача NULL или 0 будет путать версию int .

Приведение типа nullptr_t к интегральному типу требует a reinterpret_cast и имеет ту же семантику, что и приведение (void*)0 к интегральному типу (определенная реализация реализации). A reinterpret_cast не может преобразовать nullptr_t в любой тип указателя. По возможности используйте неявное преобразование или используйте static_cast .

Стандарт требует, чтобы sizeof(nullptr_t) был sizeof(void*) .

46 nik [2009-08-15 20:02:00]

Новое ключевое слово nullptr С++ 09 обозначает константу rvalue, которая служит в качестве универсального литерала нулевого указателя, заменяя багги и слабо типизированный литерал 0 и печально известный NULL-макрос. Таким образом, nullptr положит конец более чем 30-летнему смущению, двусмысленности и ошибкам. В следующих разделах представлен объект nullptr и показано, как он может исправить недомогания NULL и 0.

27 Motti [2009-08-16 09:46:00]

Когда у вас есть функция, которая может получать указатели более чем на один тип, тогда вызов с помощью NULL неоднозначен, способ, которым это работает сейчас, очень взломан, принимая int и предполагая, что он NULL .

В C++11 вы могли бы перегрузить nullptr_t , чтобы ptr p(42); представляла собой ошибку времени компиляции, а не время выполнения assert .

nullptr не может быть присвоен integral type , например int, но только тип pointer ; либо встроенный тип указателя, например int *ptr , либо интеллектуальный указатель, например std::shared_ptr

Я считаю, что это важное различие, поскольку NULL все еще можно назначить integral type и a pointer , поскольку NULL — это макрос, расширенный до 0 , который может служить как начальным значением для int , а также pointer .

Кроме того, у вас есть другой пример (помимо Википедии), где nullptr превосходит старые добрые 0?

Да. Это также (упрощенный) реальный пример, который произошел в нашем производственном коде. Он только выделялся, потому что gcc смог выдать предупреждение при перекрестном соединении с платформой с различной шириной регистра (все еще не уверен точно, почему только при перекрещивании с x86_64 на x86 предупреждает warning: converting to non-pointer type ‘int’ from NULL ):

Рассмотрим этот код (С++ 03):

Он выводит этот результат:

3 KTC [2009-08-15 20:00:00]

Это ключевое слово, потому что стандарт укажет его как таковой.;-) Согласно последнему публичному проекту (n2914)

2.14.7 Литералы указателя [lex.nullptr]

Литералом указателя является ключевое слово nullptr . Это значение типа std::nullptr_t .

Это полезно, потому что он не неявно преобразуется в интегральное значение.

Ну, на других языках зарезервированы слова, которые являются экземплярами типов. Python, например:

Это на самом деле довольно близкое сравнение, потому что None обычно используется для чего-то, что не было инициализировано, но в то же время сравнения, такие как None == 0 , являются ложными.

С другой стороны, в plain C, NULL == 0 возвращает true IIRC, потому что NULL — это просто макрос, возвращающий 0, который всегда является недопустимым адресом (AFAIK).

1 e zi [2017-10-21 00:14:00]

Скажем, что у вас есть функция (f), которая была перегружена, чтобы взять как int, так и char *. Перед С++ 11, если вы хотите называть его нулевым указателем, и вы использовали NULL (т.е. Значение 0), вы вызывали бы тот, который был перегружен для int:

Это, вероятно, не то, что вы хотели. С++ 11 разрешает это с помощью nullptr; Теперь вы можете написать следующее:

NULL не обязательно должно быть 0. Поскольку вы всегда используете NULL и никогда не 0, NULL может быть любым значением. Предполагая, что вы программируете микроконтроллер von Neuman с плоской памятью, у которого есть свои прерывающие векторы в 0. Если NULL равно 0, а что-то пишет с помощью указателя NULL, микроконтроллер падает. Если NULL позволяет указывать 1024, а 1024 — зарезервированная переменная, запись не приведет к ее краху, и вы можете обнаружить назначения NULL Pointer изнутри программы. Это бессмысленно на ПК, но для космических зондов, военного или медицинского оборудования важно не сбой.

kalnitsky’s way

Как и обещал, я продолжаю публикации на тему нового стандарта C++11. В прошлой статье я рассказал о таких вещах как:

  • автоматическое выведение типов с помощью auto;
  • определение типа с помощью decltype;
  • закрытие вложенных шаблонов;
  • цикл range-based for.

Не много, конечно, но и не мало. Но, как знает читатель, это лишь малая толика того, что дарует нам новый стандарт.

nullptr

В C++11 для обнуления указателей появилось специальное ключевое слово nullptr. В более ранних стандартах, официально использовалась запись:

Либо же вариант с макросом NULL, перекочевавшим из языка C. Проблема очевидна: для обнуления указателя используется целое число, из-за чего могут возникать мелкие неприятности. Например, при перегрузке функции:

Читать еще:  Https sch1210 sharepoint com

Какой вариант функции вызовется? Очевидно, что первый, принимающий целое число. А мы, может, хотели вызвать второй вариант, только с нулевым указателем. Для решения этой проблемы пришлось бы кастовать к указателю:

Но эта ситуация несколько надумана. Признаться, я никогда не встречался с ней в реальном проекте. Но есть более реальная. Допустим, у нас есть некоторый контейнер состоящий из указателей. И мы хотим его обнулить. Вспоминая чудесные алгоритмы из STL, мы не раздумывая применим std::fill .

На первый взгляд — все просто отлично! Но ошибка есть, и она такая же как и в предыдущем варианте. std::fill является шаблоном, и увидев 0, шаблон примет его за int и, конечно же, из-за несоответствия типов мы получим очень страшное сообщение об ошибке от компилятора. Выход был такой же — кастовать 0 к указателю, что уж явно не повышает читабельность.

Именно поэтому, было принято новое ключевое слово, и имя ему nullptr. Используя это ключевое слово, мы избавимся от вышеописанной проблемы, так как nullptr имеет свой собственный тип — std::nullptr_t — и компилятор не спутает его ни с чем другим.

Список инициализации

В предыдущем стандарте возможности списков инициализации были чрезвычайно малы. Что мы могли сделать раньше? Лишь проинициализировать некоторую структуру, да некоторый массив:

Но C++ предоставляет более удобные, более гибкие средства разработки. Я говорю о классах и контейнерах.

C++11 наконец разрешает эту несправедливость, путем введения шаблонного класса std::initializer_list<>. Все контейнеры отныне обладают конструктором, принимающим список инициализации ( std::initializer_list<> ), отчего становится реальной следующая запись:

Стоит отметить, что списки инициализации используются не только для инициализации. Например, с помощью последних теперь можно добавлять в контейнер несколько элементов.

Разработчик может оборудовать свой класс (в частности контейнер) подобной возможностью. Надо всего лишь определить конструктор принимающий std::initializer_list<> .

Объекты std::initializer_list<> не могут быть изменены.

Универсальная инициализация

Списки инициализации — это хорошо. Но разработчики решили на этом не останавливаться и пошли еще дальше. Они расширили синтаксис списка инициализации, позволив вытворять следующие вещи:

Подобная инициализация вызовет конструктор, как будто бы мы написали:

Универсальная инициализация работает как для классов, так и для структур. В случае классов вызовется конструктор, а в случае структур — будет происходить поэлементная инициализация в порядке объявления атрибутов.

Если не указать последний атрибут (или атрибуты), то для него вызовется конструктор по-умолчанию. Для встроенного типа (например int) произойдет инициализация нулем.

Стоит отметить, что такая инициализация позволяет писать следующие вещи:

Интересен тот факт, что универсальная инициализация защищает от неявных преобразований.

Познакомившись с универсальной инициализацией и списками инициализации, может возникнуть вопрос: «А какой конструктор вызовется в следующей ситуации?»

В этом случае, при создании объекта foo вызовется конструктор Foo(int x, int y), а при создании barFoo(std::initializer_list list). В случае, если последний конструктор будет отсутствовать, то все пойдет как обычно: в обеих случаях вызовется первый конструктор.

Вместо заключения

Хотелось написать больше, но получилось совсем чуть-чуть. Найти время для творчества — это действительно проблема. Ну что же, самые широко известные (за исключением лямбд) нововведения я затронул. В дальнейшем напишу о менее известный вещах.

Урок №81. Нулевые указатели

Обновл. 31 Дек 2019 |

Как и в случае с обычными переменными, указатели не инициализируются при создании. Если значение не было присвоено, то указатель по умолчанию будет указывать на любой адрес, содержимым которого является мусор.

Нулевое значение и нулевые указатели

Помимо адресов памяти, есть ещё одно значение, которое указатель может хранить: значение null. Нулевое значение (или ещё «значение null») — это специальное значение, которое означает, что указатель ни на что не указывает. Указатель, содержащий значение null, называется нулевым указателем.

В C++ мы можем присвоить указателю нулевое значение, инициализируя его/присваивая ему литерал 0 :

Поскольку значением нулевого указателя является нуль, то это можно использовать внутри условного ветвления для проверки того, является ли указатель нулевым или нет:

Однако, поскольку NULL является макросом препроцессора и, технически, не является частью C++, то его не рекомендуется использовать.

nullptr в C++11

Обратите внимание, значение 0 не является типом указателя, и присваивание указателю 0 для обозначения того, что он является нулевым — немного противоречиво, не так ли? В редких случаях, использование 0 как аргумента-литерала может привести к проблемам, так как компилятор не сможет определить, используется ли нулевой указатель или целое число 0:

Для решения этой проблемы, в C++11 ввели новое ключевое слово nullptr, которое также является константой r-value.

Начиная с C++11, при работе с нулевыми указателями, использование nullptr является более предпочтительным вариантом, нежели использование 0:

C++ неявно преобразует nullptr в соответствующий тип указателя. Таким образом, в примере выше, nullptr неявно преобразуется в указатель типа int, а затем значение nullptr присваивается для ptr .

nullptr также может использоваться для вызова функции (в качестве аргумента-литерала):

Курсы «C#.NET Developer»

Курсы «Java Developer»

Курсы «Frontend Developer»

Курсы «JavaScript Developer»

Курсы «Python Developer»

Курсы «Unity/Game Developer»

Поделиться в социальных сетях:

Урок №80. Указатели

Комментариев: 6

с этими указателями….какие-то скороговорки для мозга..

Так и не понял, для чего нужен std::nullptr_t. Зачем его ввели.

Судя по тому, что я узнал за 81 урок и какое мнение сложилось о С++, ответ на Ваш вопрос: «просто так, на всякий пожарный»))))))

ps. 3 допустимых вида инициализации переменных, значит, вас не смущают?)))))

В этих уроках объясняется как база, так и нюансы, которые вы, скорее всего, не очень часто будете использовать на практике, но знать об этом стоит.

Каждая переменная или константа должна быть определенного типа.
Следовательно, у nullptr (как константы, а не ключевого слова) тоже должен быть какой-то тип.
В MSDN есть пример, показывающий зачем ввели дополнительный тип:

Например, если для функции func(std::pair ) произвести вызов func(std::make_pair(NULL, 3.14)), возникнет ошибка времени компиляции. Макрос NULL разворачивается в 0, поэтому вызов std::make_pair(0, 3.14) возвращает значение std::pair , которое невозможно преобразовать к типу параметра std::pair функции func(). Вызов func(std::make_pair(nullptr, 3.14)) успешно компилируется, поскольку std::make_pair(nullptr, 3.14) возвращает значение std::pair , которое допускает преобразование в тип std::pair .

Читать еще:  List point c

Зачем еще оно нужно:
Приходите на собеседование, и дают вам такой код

pointer — nullptr

checking if pointer points within an array (8)

No, it’s not available. The standard says that pointers are only comparable with the built-in operators when they point to the same array or other memory block. That is, the block needs to have been allocated all at once, as opposed to two separate allocations that might happen to be adjacent to each other.

This is undefined:

Both values are members of the same struct, so they belong to the same block of memory (namely, f ‘s).

Practically speaking, though, there’s nothing wrong with your custom-defined comparison.

However, your custom specialization is unnecessary, since the std::less template is defined for pointers, evidently. So this is OK:

There’s still no indication of what the result must be, but you’re at least promised to have some result, as opposed to undefined behavior. You get a total order, but you don’t know what order until you run it.

Can I check whether or not a given pointer points to an object within an array, specified by its bounds?

Or do the pointer comparisons invoke undefined behavior if p points outside the bounds of the array? In that case, how do I solve the problem? Does it work with void pointers? Or is it impossible to solve?

A stack-based STL allocator is of such limited utility that I doubt you will find much prior art. Even the simple example you cite quickly blows up if you later decide you want to copy or lengthen the initial lstring .

For other STL containers such as the associative ones (tree-based internally) or even vector and deque which use either a single or multiple contiguous blocks of RAM, the memory usage semantics quickly become unmanageable on the stack in almost any real-world usage.

Apparently, there is a conforming Stack Allocator from one Howard Hinnant.

It works by using a fixed size buffer (via a referenced arena object) and falling back to the heap if too much space is requested.

This allocator doesn’t have a default ctor, and since Howard says:

I’ve updated this article with a new allocator that is fully C++11 conforming.

I’d say that it is not a requirement for an allocator to have a default ctor.

Arrays are guaranteed to be contiguous in memory, so just check that the pointer is within the bounds of the address of the first element and the last.

Two pointers of different types cannot be compared unless:

  • One type is a class type derived from the other type.
  • At least one of the pointers is explicitly converted (cast) to type void *. (The other pointer is implicitly converted to type void * for the conversion.)

So a void* can be compared to anything else (including another void* ). But will the comparison produce meaningful results?

If two pointers point to elements of the same array or to the element one beyond the end of the array, the pointer to the object with the higher subscript compares higher. Comparison of pointers is guaranteed valid only when the pointers refer to objects in the same array or to the location one past the end of the array.

Looks like not. If you don’t already know that you are comparing items inside the array (or just past it), then the comparison is not guaranteed to be meaningful.

There is, however, a solution: The STL provides std::less<> and std::greater<> , which will work with any pointer type and will produce valid results in all cases:

Update:

The answer to this question gives the same suggestion ( std::less ) and also quotes the standard.

The C++ standard does not specify what happens when you are comparing pointers to objects that do not reside in the same array, hence undefined behaviour. However, the C++ standard is not the only standard your platform must conform. Other standards like POSIX specify things that C++ standard leaves as undefined behaviour. On platforms with virtual address space like Linux and Win32/64 you can compare any pointers without causing any undefined behaviour.

Check if a pointer points inside an array in standard C/C++

It depends on what you mean by UB.

Specifically, for pointer comparisons, section 5.9 «Relational Operators» of the C++ standard says:

If two pointers p and q of the same type point to different objects that are not members of the same object or to different functions or if only one of them is null, the results of p , p>q , p and p>=q are unspecified.

Note that the behaviour is unspecified (meaning that the result of the comparison could be true or false — in other words the result doesn’t tell you anything useful — but the implementation isn’t required to specify which) as opposed to undefined (meaning that the compiler or the resulting program could do anything at all).

However, so far I have only seen one family of implementations that didn’t do the expected thing with code like Kirill’s:

Those implementations are compilers intended for building MS-DOS real-mode programs, in which addresses have a paragraph and offset part. The addresses FF00:0010 and FF01:0000 point to the same memory location but if I recall correctly the compilers weren’t guaranteed to behave in the expected way, except when compiling for some memory models (certainly the HUGE model but perhaps others).

Читать еще:  Телевизор samsung не видит wifi роутер

However, if either p or q does not point to an existing object (because for example the pointer was freed) then the behaviour is going to be undefined no matter what you do. So you can’t use this kind of method to figure out if a pointer is still valid.

Stack-buffer based STL allocator?

It’s definitely possible to create a fully C++11/C++14 conforming stack allocator*. But you need to consider some of the ramifications about the implementation and the semantics of stack allocation and how they interact with standard containers.

Here’s a fully C++11/C++14 conforming stack allocator (also hosted on my github):

This allocator uses a user-provided fixed-size buffer as an initial source of memory, and then falls back on a secondary allocator ( std::allocator by default) when it runs out of space.

Things to consider:

Before you just go ahead and use a stack allocator, you need to consider your allocation patterns. Firstly, when using a memory buffer on the stack, you need to consider what exactly it means to allocate and deallocate memory.

The simplest method (and the method employed above) is to simply increment a stack pointer for allocations, and decrement it for deallocations. Note that this severely limits how you can use the allocator in practice. It will work fine for, say, an std::vector (which will allocate a single contiguous memory block) if used correctly, but will not work for say, an std::map , which will allocate and deallocate node objects in varying order.

If your stack allocator simply increments and decrements a stack pointer, then you’ll get undefined behavior if your allocations and deallocations are not in LIFO order. Even an std::vector will cause undefined behavior if it first allocates a single contiguous block from the stack, then allocates a second stack block, then deallocates the first block, which will happen every time the vector increases it’s capacity to a value that is still smaller than stack_size . This is why you’ll need to reserve the stack size in advance. (But see the note below regarding Howard Hinnant’s implementation.)

Which brings us to the question .

What do you really want from a stack allocator?

Do you actually want a general purpose allocator that will allow you to allocate and deallocate memory chunks of various sizes in varying order, (like malloc ), except it draws from a pre-allocated stack buffer instead of calling sbrk ? If so, you’re basically talking about implementing a general purpose allocator that maintains a free list of memory blocks somehow, only the user can provide it with a pre-existing stack buffer. This is a much more complex project. (And what should it do if it runs out space? Throw std::bad_alloc ? Fall back on the heap?)

The above implementation assumes you want an allocator that will simply use LIFO allocation patterns and fall back on another allocator if it runs out of space. This works fine for std::vector , which will always use a single contiguous buffer that can be reserved in advance. When std::vector needs a larger buffer, it will allocate a larger buffer, copy (or move) the elements in the smaller buffer, and then deallocate the smaller buffer. When the vector requests a larger buffer, the above stack_allocator implementation will simply fall back to a secondary allocator (which is std::allocator by default.)

Again, this works fine for vector — but you need to ask yourself what exactly you intend to do with the stack allocator. If you want a general purpose memory allocator that just happens to draw from a stack buffer, you’re talking about a much more complex project. A simple stack allocator, however, which merely increments and decrements a stack pointer will work for a limited set of use cases. Note that for non-POD types, you’ll need to use std::aligned_storage to create the actual stack buffer.

I’d also note that unlike Howard Hinnant’s implementation, the above implementation doesn’t explicitly make a check that when you call deallocate() , the pointer passed in is the last block allocated. Hinnant’s implementation will simply do nothing if the pointer passed in isn’t a LIFO-ordered deallocation. This will enable you to use an std::vector without reserving in advance because the allocator will basically ignore the vector’s attempt to deallocate the initial buffer. But this also blurs the semantics of the allocator a bit, and relies on behavior that is pretty specifically bound to the way std::vector is known to work. My feeling is that we may as well simply say that passing any pointer to deallocate() which wasn’t returned via the last call to allocate() will result in undefined behavior and leave it at that.

*Finally — the following caveat: it seems to be debatable whether or not the function that checks whether a pointer is within the boundaries of the stack buffer is even defined behavior by the standard. Order-comparing two pointers from different new / malloc ‘d buffers is arguably implementation defined behavior (even with std::less ), which perhaps makes it impossible to write a standards-conforming stack allocator implementation that falls back on heap allocation. (But in practice this won’t matter unless you’re running a 80286 on MS-DOS.)

** Finally (really now), it’s also worth noting that the word «stack» in stack allocator is sort of overloaded to refer both to the source of memory (a fixed-size stack array) and the method of allocation (a LIFO increment/decrement stack pointer). When most programmers say they want a stack allocator, they’re thinking about the former meaning without necessarily considering the semantics of the latter, and how these semantics restrict the use of such an allocator with standard containers.

Ссылка на основную публикацию
Adblock
detector