All streams
Search
Write a publication
Pull to refresh
16
0
Send message
В классе присутствуют только конструктор перемещения и оператор перемещения, однако инициализация и присваивание ресурса происходит по значению, что может грозить серьезными ошибками в некоторых случаях, что неприемлемо для «универсальной» обертки.
Пример с std::unique_ptr принципиально некорректен, а спекуляция с использованием std::remove_pointer указывает на полное непонимание принципа его работы. В случае std::unique_ptr работу с ресурсом необходимо оборачивать в работу над указателем на ресурс, а не маскировать ресурс под указатель.

Кстати последняя ревизия Generic Scope Guard and RAII Wrapper — N4189.
Оригинальная статья опубликована в выпуске 126 журнала Overload (апрель 2015).

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

Эта проблема решена в следующем стандарте — int std::uncaught_exceptions(). В конструкторе сохраняем текущее количество исключений, а в деструкторе сравниваем с сохраненным.
Впрочем работает это только при вызове constexpr варианта, при попытке вызова от аргумента, который не известен на этапе компиляции, компилятору придется включить этот код в объектный файл. Но мы же говорим про константы этапа компиляции, не так ли? Для одиночных констант такой проблемы не будет (конечено если не пытаться брать адрес constexpr функции).
a.cpp:
#include <cstdio>

#include "h.h"

void printSin1() {
    for (int i =0;i<4096;++i)
        printf("%f\n", sin_table(i));
}

int main() {
    printSin1();
    printSin2();
}

b.cpp:
#include <cstdio>

#include "h.h"

void printSin2() {
    for (int i =0;i<4096;++i)
        printf("%f\n", sin_table(i));
}

h.h:
struct sin_table_t {
    float values[4096];
};

constexpr float sin_table(int i) {
    return sin_table_t{{/* */}}.values[i];
}

void printSin1();
void printSin2();
В чем нарушается то?

${CC} ${CCFLAGS} -c -o $@ $<
=>
PVS-Studio -cc=gcc — ${CCFLAGS} -c -o $@ $<

На выходе получим $@ и $@.pvs

${LD} -o ${TARGET} ${OBJS} ${LIBS}
=>
PVS-Studio -ld=gcc — -o ${TARGET} ${OBJS} ${LIBS}

На выходе получаем ${TARGET} и готовый лог
Соединить логи в один можно там, где работает линковщик.
Внимательно изучив код, я понял, что немного ошибся — это никакая не оптимизация, первый проход — холостой — делается для расчета длины, а второй для операций над строкой, о чем и написано в комментарии (которые мой внутренний парсер давно привык игнорировать)
/*
* First round: compute the length and allocate memory.
* Second round: copy the text.
*/

Так как выделение памяти происходит исходя из расчетной длины, «неоптимизированный» вариант с выделением памяти заранее невозможен.
А разделение на две процедуры породило бы дублирование кода в обе процедуры, что грозит ошибками в случае его модификации. Однако, сделать его более «качественным» можно было бы, например, заменой абстрактного int round на bool length_calculation_only.
Это уже похоже на ошибку. Анализатор говорит о лишней проверке, но на самом деле проблема прежде всего в другом. Указатель retval инициализирован 0, и я не нашел ни одного места в этой функции, где бы его значение менялось. При этом в него делается много раз делается strncpy. После чего его внезапно решили проверить на NULL.

char_u	*retval = NULL;
...
for (round = 1; round <= 2; ++round)
{
    ...
    if (lnum < 0 || submatch_mmatch->endpos[no].lnum < 0)
	return NULL;

    ...
    if (s == NULL)
	break;
    ...
	if (round == 2)
	    vim_strncpy(retval, s, len);
	...
	if (round == 2)
	{
	    STRCPY(retval, s);
	    retval[len] = '\n';
	}
	...
    if (round == 2)
	STRCPY(retval + len, s);
   	...
    if (round == 2)
	retval[len] = '\n';
	...
	if (round == 2)
	    STRNCPY(retval + len, reg_getline_submatch(lnum),
				     submatch_mmatch->endpos[no].col);
	...
	if (round == 2)
	    retval[len] = NUL;
	...
    if (retval == NULL)
    {
	retval = lalloc((long_u)len, TRUE);
	if (retval == NULL)
	    return NULL;
    }
}

Доступ по указателю осуществляется только на втором раунде, а аллокация на первом. Такая вот агрессивная оптимизация, чтобы не делать лишней аллокации, если до второго раунда дело не дойдет. Ошибки здесь нет.
в следующий стандарт языка программирования C будут добавлены средства ООП, а именно — классы

Первое предложение добавить классы в C было еще в 93-м году — N298. Затем в 95-ом — N424, N445-447. Если вы внимательно следите за предложениями для WG14 и WG21, то могли бы заметить, что далеко не всякое предложение попадает в стандарт, а некоторые хотелось бы и вовсе «развидеть».
Для С вопрос разъясняется в

Вы вырываете из контекста:
If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.102)

Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer, an
address inappropriately aligned for the type of object pointed to, and the address of an object after the
end of its lifetime

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

Это неизбежно, когда ненароком ставят рядом два довольно-таки разных языка (более эпичный вариант лишь Java/JavaScript). ;)

Я мог бы с вами согласиться на 100%, если бы все четыре основных компилятора не были бы C/C++.
Тут нельзя делать заключение о значении указателя на основе лишь синтаксиса, поскольку нет разыменовывания — интерпретация выражения не выходит за рамки арифметики указателей.

В стандарте нигде не определено, считать ли такой случай разыменованием или нет. Аргументом к тому, что «считать», может быть пример ниже про виртуальное наследование. А так как в стандарте ничего не сказано, про то, что данный случай «не считается разыменованием», это и называется «неопределенным поведением», так как стандартом оно не определено.
Все действия, помимо тривиальных GET, POST, PUT, DELETE, выносятся в отдельную коллекцию:
POST /missiles/{id}/actions/launch
POST /missiles/{id}/actions/sell

Нет ничего неправильного в том, чтобы писать небезопасный код — не нужно платить за то, что не используется, если аргумент, приводящий к UB, по логике программы не может быть использован, обработкой исключений можно безболезненно пожертвовать.

struct usb_line6 *line6 = &podhd->line6;

Таким образом здесь мы соглашаемся, что podhd!=nullptr, и подобный аргумент не должен быть использован при вызове функции. Неопределенного поведения в этой строке нет.

if ((interface == NULL) || (podhd == NULL))

Однако дальнейшая проверка podhd уже является неопределенным поведением, так как существуют два конкурирующих способа осуществить эту проверку, возможно дающих разный ответ: проверить значение указателя, или воспользоваться ранее выведенной аксиомой, что podhd!=nullptr.
Это только с точки зрения компилятора/оптимизатора взятие адреса в данном случае не является разыменованием, соответственно и UB. Однако с точки зрения стандарта текст программы нужно читать буквально: есть "->" — есть разыменование, а если по указателю может быть 0 — то и неопределенное поведение. Собственно конкретная работа оптимизатора никак не описана в стандарте и, соответственно, является UB. А для соответствия стандарту от компилятора требуется строго ожидаемого поведения от буквально прочитанного кода, поэтому неопределенность оптимизатора никогда не проявляется в корректном коде. А если в этом коде присутствует формальный UB (даже если с точки зрения компилятора это не так), можно ожидать проявления UB со стороны оптимизатора.
Поясню:
Происходит ли здесь присваивание?
x = x;


Суммирование?
y = x + 0;


Разыменование?
&a->b
Проблема в неверной постановке вопроса:
«разыменовывание нулевого указателя» однозначно по стандарту является неопределенным поведением.
А вопрос на самом деле заключается в следующем: считается ли конструкция "&a->b" разыменованием указателя a.
А будет ли неопределённое поведение в таком гипотетическом случае?
static int podhd_try_init(struct usb_interface *interface,
        struct usb_line6_podhd * volatile podhd)
{
  int err;
  struct usb_line6 *line6 = &podhd->line6;

  if ((interface == NULL) || (podhd == NULL))
    return -ENODEV;
  ....
}
template<class T>
typename std::enable_if<std::is_base_of<Base1,T>::value,void>::type
execute(const T&);

template<class T>
typename std::enable_if<std::is_base_of<Base2,T>::value,void>::type
execute(const T&);
P.S.: Атомарные типы впрочем не нужны в данном случае.
P. S. Несложно из приведённого кода догадаться, что это «одноразовый» барьер: как только через него пройдут все потоки, вы не сможете повторно использовать этот же экземпляр класса в качестве барьера.

Несложно упростить код до многоразового:
class barrier {
 const unsigned int threadCount;
 std::atomic<unsigned int>threadsWaiting;
 std::condition_variable waitVariable;
 std::mutex mutex;
public:
 barrier(unsigned int n) : threadCount(n) {
  threadsWaiting = 0;
}
barrier(const barrier &) = delete;
 void wait() {
  std::unique_lock<std::mutex> lock(mutex);
  if (threadsWaiting.fetch_add(1) >= threadCount - 1) {
   threadsWaiting.store(0);
   waitVariable.notify_all();
 }
 else {
  waitVariable.wait(lock);
 }
}
};

Information

Rating
Does not participate
Location
Москва и Московская обл., Россия
Date of birth
Registered
Activity

Specialization

Software Architect
PostgreSQL
C#
C++
Linux
Docker
Kubernetes
High-loaded systems
Designing application architecture
Database design