使用 BOLDDAY 宏解决代码分析警告(与 CMonthCalCtrl 一起使用)

Resolving code analysis warnings with the BOLDDAY macro (used with CMonthCalCtrl)

我在 CMonthCalCtrl 控件和代码现代化方面遇到了一些问题。第一个问题与 BOLDDAY 宏有关。

这个宏是用来调整day states(在日历上把具体的日期加粗),这个概念有详细的描述here。如文档所述,您需要定义一个宏:

#define  BOLDDAY(ds, iDay) if(iDay > 0 && iDay < 32) \
            (ds) |= (0x00000001 << (iDay-1))

这是我使用此宏的代码,以便您了解一些上下文:

void CMeetingScheduleAssistantDlg::InitDayStateArray(int iMonthCount, LPMONTHDAYSTATE pDayState, COleDateTime datStart)
{
    int                 iMonth = 0;
    COleDateTimeSpan    spnDay;
    CString             strKey;
    SPECIAL_EVENT_S     *psEvent = nullptr;

    if (pDayState == nullptr)
        return;

    memset(pDayState, 0, sizeof(MONTHDAYSTATE)*iMonthCount);

    if (m_pMapSPtrEvents == nullptr && m_Reminders.Count() == 0)
    {
        return;
    }

    spnDay.SetDateTimeSpan(1, 0, 0, 0);

    auto datDay = datStart;
    const auto iStartMonth = datStart.GetMonth();
    auto iThisMonth = iStartMonth;
    auto iLastMonth = iThisMonth;
    do
    {
        strKey = datDay.Format(_T("%Y-%m-%d"));

        if (m_pMapSPtrEvents != nullptr)
        {
            psEvent = nullptr;
            m_pMapSPtrEvents->Lookup(strKey, reinterpret_cast<void*&>(psEvent));
            if (psEvent != nullptr)
            {
                BOLDDAY(pDayState[iMonth], datDay.GetDay());
            }
        }

        if (m_Reminders.HasReminder(datDay))
        {
            BOLDDAY(pDayState[iMonth], datDay.GetDay());
        }

        datDay = datDay + spnDay;
        iThisMonth = datDay.GetMonth();
        if (iThisMonth != iLastMonth)
        {
            iLastMonth = iThisMonth;
            iMonth++;
        }
    } while (iMonth < iMonthCount);
}

我在任何地方使用此 BOLDDAY 宏都会收到代码分析警告 (C26481):

warning C26481: Don't use pointer arithmetic. Use span instead (bounds.1).

我不清楚问题出在 BOLDDAY 宏还是我自己的代码上?


更新

当我将宏转换为函数时仍然收到警告:


更新 2

如果有帮助,我目前通过以下方式调用 InitDayStateArray 函数:

void CMeetingScheduleAssistantDlg::SetDayStates(CMonthCalCtrl &rCalendar)
{
    COleDateTime        datFrom, datUntil;

    const auto iMonthCount = rCalendar.GetMonthRange(datFrom, datUntil, GMR_DAYSTATE);
    auto pDayState = new MONTHDAYSTATE[iMonthCount];
    if (pDayState != nullptr)
    {
        InitDayStateArray(iMonthCount, pDayState, datFrom);
        VERIFY(rCalendar.SetDayState(iMonthCount, pDayState));
        delete[] pDayState;
    }
}
void CMeetingScheduleAssistantDlg::OnGetDayStateEnd(NMHDR* pNMHDR, LRESULT* pResult)
{
    NMDAYSTATE* pDayState = reinterpret_cast<NMDAYSTATE*>(pNMHDR);
    MONTHDAYSTATE   mdState[3]{}; // 1 = prev 2 = curr 3 = next
    const COleDateTime  datStart(pDayState->stStart);

    if (pDayState != nullptr)
    {
        InitDayStateArray(pDayState->cDayState, &mdState[0], datStart);
        pDayState->prgDayState = &mdState[0];
    }

    if (pResult != nullptr)
        *pResult = 0;
}

也许如果 LPMONTHDAYSTATE 信息的容器以某种方式进行调整,它将有助于解决这个 span 问题?

Microsoft 提供的示例代码曾经作为可同时使用 C 和 C++ 编译器进行编译的代码发布。这限制了语言功能的可用性,经常生成特别是 C++ 客户端不应该逐字使用的代码。

这里的情况是 BOLDDAY 类似函数的宏,它解决了 C 中没有引用类型的问题。另一方面,C++ 有,宏可以替换为函数:

void bold_day(DWORD& day_state, int const day) noexcept {
    if (day > 0 && day < 32) {
        day_state |= (0x00000001 << (day - 1));
    }
}

使用此函数代替 BOLDDAY 宏可使 C26481 诊断静音。

虽然这有效,但我完全无法理解编译器在宏版本中看到指针算法的位置。无论如何,在可能的情况下用实际函数(或函数模板)替换类似函数的宏总是可取的。

更新

现在事情开始变得有意义了。如上所述,虽然用函数替换类函数宏是可取的,但它不会解决问题。我的测试碰巧使用了 pDayState[0],它仍然为宏引发 C26481,但没有为函数引发。而是使用 pDayState[1],在任何一种情况下都会引发诊断。

让我们把拼图的各个部分放在一起:回想一下,当 p 是指针类型并且 N 整型。这解释了为什么编译器在看到 pDayState[iMonth].

时抱怨 "pointer arithmetic"

解决这个问题相当简单。根据诊断的建议,使用 std::span(需要 C++20)。对 InitDayStateArray() 的以下更改使 C26481 诊断消失:

void CMeetingScheduleAssistantDlg::InitDayStateArray(int iMonthCount,
        LPMONTHDAYSTATE pDayState,
        COleDateTime datStart)
{
    std::span const day_month_state(pDayState, iMonthCount);
    // ...

    // memset(pDayState, 0, sizeof(MONTHDAYSTATE)*iMonthCount);
    std::fill(begin(day_month_state), end(day_month_state), 0);

    // ...

    do
    {
        // ...
            {
                bold_day(day_month_state[iMonth], datDay.GetDay());
            }
        }

        if (m_Reminders.HasReminder(datDay))
        {
            bold_day(day_month_state[iMonth], datDay.GetDay());
        }
        // ...
    } while (iMonth < day_month_state.size());
}

A std::span “描述了一个可以引用连续对象序列的对象”。它采用描述数组的分解指针和大小参数,并将它们重新组合成一个对象,恢复数组的完整保真度。

听起来不错。但请记住,这是 C++,有一个警告:就像它邪恶的 C++17 祖先 std::string_view 一样,std::span 是一个毫不犹豫的悬挂指针工厂。您可以自由地传递它们,并在引用数据还活着的时候继续使用它们。这对于 每个 专业化都是有保证的,从 C++23 开始。

另一个问题是,解决这个诊断问题现在有几个突然冒出来的问题,这表明 std::span 不够好,应该改用 gsl::span。解决这些问题可能需要再进行一次问答。