C++ 中文周刊 2024-01-26 第147期

周刊项目地址

公众号

qq群 点击进入

RSS https://github.com/wanghenshui/cppweeklynews/releases.atom

欢迎投稿,推荐或自荐文章/软件/资源等

提交 issue 或评论区留言

本期文章由 不语 沧海 彩虹蛇皮虾 赞助

jetbrain发布了23年 c++ 生态回顾 https://blog.jetbrains.com/clion/2024/01/the-cpp-ecosystem-in-2023/

感兴趣的可以看看,没啥意思


资讯

标准委员会动态/ide/编译器信息放在这里

一月邮件

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/#mailing2024-01

文章

全新的构造函数,C++ 中的 relocate 构造函数

其实这个概念之前讨论了很久,老熟人Arthur O’Dwyer 提了很多相关的提案 patch。大家感兴趣的可以读一下。算是一个优化的点

之前也提到过,比如

讲trivial relocation的现状以及开源实现

T.r. types Non-t.r. types Throwing-move types Rightward motion (`insert`) Leftward motion (`erase`) Non-pointer iterators
STL Classic (non-relocating) std::copy N/A N/A UB
std::copy_n N/A N/A UB UB
std::copy_backward N/A N/A UB
cstring memcpy UB UB UB SFINAE
memmove UB SFINAE
Qt q_uninitialized_relocate_n ✓? UB UB SFINAE
q_relocate_overlap_n SFINAE
BSL destructiveMove UB UB SFINAE
P2786R0 trivially_relocate SFINAE SFINAE SFINAE
relocate SFINAE SFINAE
move_and_destroy SFINAE UB ?
P1144R6 uninitialized_relocate UB
uninitialized_relocate_n UB
P1144R7 uninitialized_relocate_backward UB

还给人folly提MR https://github.com/facebook/folly/pull/1934

等等,周边信息很多

why gcc and clang sometimes emit an extra mov instruction for std::clamp on x86

直接贴代码 https://godbolt.org/z/rq9dsGxh5

#include <algorithm>

double incorrect_clamp(double v, double lo, double hi){
    return std::min(hi, std::max(lo, v));
}

double official_clamp(double v, double lo, double hi){ 
    return std::clamp(v, lo, hi); 
}

double official_clamp_reordered(double hi, double lo, double v){ 
    return std::clamp(v, lo, hi); 
}

double correct_clamp(double v, double lo, double hi){
    return std::max(std::min(v, hi), lo);
}

double correct_clamp_reordered(double lo, double hi, double v){
    return std::max(std::min(v, hi), lo);
}

对应的汇编

incorrect_clamp(double, double, double):
        maxsd   xmm0, xmm1
        minsd   xmm0, xmm2
        ret
official_clamp(double, double, double):
        maxsd   xmm1, xmm0
        minsd   xmm2, xmm1
        movapd  xmm0, xmm2
        ret
official_clamp_reordered(double, double, double):
        maxsd   xmm1, xmm2
        minsd   xmm0, xmm1
        ret
correct_clamp(double, double, double):
        minsd   xmm2, xmm0
        maxsd   xmm1, xmm2
        movapd  xmm0, xmm1
        ret
correct_clamp_reordered(double, double, double):
        minsd   xmm1, xmm2
        maxsd   xmm0, xmm1
        ret

为什么正确的代码多了一条 mov xmm?

浮点数 +-0的问题,标准要求返回第一个参数,比如 std::clamp(-0.0f, +0.0f, +0.0f)

如果配置了-ffinite-math-only -fno-signed-zeros 最终汇编是一样的 https://godbolt.org/z/esMY18a5z

Fuzzing an API with libfuzzer

举一个fuzz例子,大家都学一下

你看这个接口感觉可能无从下手

extern "C"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)

我们要测试的接口长这样

template <typename T, size_t Capacity>
requires (std::is_nothrow_move_constructible_v<T> && std::is_nothrow_move_assignable_v<T>)
class fixed_stack
{
public:
    T& push(T t) {
        if (size() == capacity()) throw size_error("push on full stack");
        return data_[++size_] = std::move(t);
    }
    T& back() {
        if (empty()) throw size_error("back on empty stack");
        return data_[size_];
    }
    T pop() {
        if (empty()) throw size_error("pop on empty stack");
        return std::move(data_[size_--]);
    }
    [[nodiscard]] bool empty() const { return size() == 0; }
    [[nodiscard]] size_t size() const { return size_; }
    [[nodiscard]] static size_t capacity() { return Capacity; }
private:
    size_t size_ = 0;
    std::array<T, Capacity> data_{};
};

考虑一下测试代码

可能长这样

truct failure : std::string {
    using std::string::string;
};

#define REQUIRE(...) if (__VA_ARGS__) {;} else throw failure #__VA_ARGS__)
#define FAIL(...) throw failure(__VA_ARGS__)

int main() {
    unsigned fail_count = 0;
    struct test {
        const char* name;
        std::function<void()> f;
    };
    test tests[] {
            { "default constructed stack is empty",
              []{
                fixed_stack<int, 8> s;
                REQUIRE(s.size() == 0);
                REQUIRE(s.empty());
            }},
            { "Each push grows size by one",
              [] {
                fixed_stack<int, 8> s;
                s.push(3);
                REQUIRE(s.size() == 1);
                s.push(2);
                REQUIRE(s.size() == 2);
                s.push(8);
                REQUIRE(s.size() == 3);
              }
            },
            { "Pop returns the pushed elements in reverse order",
              []{
                fixed_stack<int, 8> s;
                s.push(3);
                s.push(2);
                s.push(8);
                REQUIRE(s.pop() == 8);
                REQUIRE(s.pop() == 2);
                REQUIRE(s.pop() == 3);
            }
            },
            { "pop on empty throws",
              []{
                fixed_stack<int, 8> s;
                s.push(3);
                s.pop();
                try {
                    s.pop();
                    FAIL("didn't throw");
                }
                catch (const size_error&)
                {
                    // good!
                }
            }}
    };
    for (auto& t : tests){
        try {
            std::cout << std::setw(60) << std::left << t.name << "\t";
            t.f();
            std::cout << "PASS!";
        } catch (const failure& f) {
            std::cout << "FAILED!\nError: " << f << '\n';
            ++fail_count;
        } catch (...) {
            std::cout <<  "FAILED!\nUnknown reason!";
            ++fail_count;
        }
        std::cout << '\n';
    }
}

现在咱们考虑怎么把这个测试代码改写成fuzz test?

简单来说输入的就是一段二进制,怎么根据这个二进制拆解出不同的动作,拆解出不同的输入?

struct exhausted {};
struct source {
    std::span<const uint8_t> input;
    template <typename T>
    requires (std::is_trivial_v<T>)
    T get() {
        constexpr auto data_size = sizeof(T);
        if (input.size() < data_size) throw exhausted{};
        alignas (T) uint8_t buff[data_size];
        std::copy_n(input.begin(), data_size, buff);
        input = input.subspan(data_size);
        return std::bit_cast<T>(buff);
    }
};
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    source s;
    std::vector<int> comparison;
    std::optional<fixed_stack<std::unique_ptr<int>, 8>> stack;
    try {
        for (;;) {
            if (!stack.has_value()) {
                stack.emplace();
            }
            //   通过source 拿一个u8来枚举动作
            const auto action = s.get<uint8_t>();
            switch (action) {
                case 0: // push
                {
                    // 通过source拿到需要的输入数据
                    const int v = s.get<int>();
                    const auto size = stack->size();
                    try {
                        stack->push(std::make_unique<int>(v));
                        comparison.push_back(v);
                        assert(stack->size() == comparison.size());
                        assert(stack->back() != nullptr);
                        assert(*stack->back() == v);
                    } catch (size_error) {
                        assert(size == stack->capacity());
                        assert(stack->size() == size);
                    }
                }
                break;
                case 1: // pop
                {
                    if (!stack->empty()) {
                        auto v = stack->pop();
                        assert(v);
                        assert(*v == comparison.back());
                        comparison.pop_back();
                    }
                }
                break;
                case 2: // destroy
                {
                    stack.reset();
                    comparison = {};
                }
            }
        }
    } catch (exhausted) { /* done */ }
    return 0;  // Values other than 0 and -1 are reserved for future use.
}

编译带上-fsanitize=address,undefined,fuzzer --coverage

能抓到错误,抓到错误调试就是另一个流程了,gdb挂上去调就行了。留做作业吧

C++ 实现 shared_ptr / weak_ptr /enable_shared_from_this

学吧,学无止境

主要是控制块的管理

C++ Lifetime Profile Static Analyzer

这个哥们把一个工具优化到能用的水平,并介绍了相关设计

这个精力投入令人佩服,代码在这里 https://github.com/qqiangwu/cppsafe

感兴趣的可以体验一下

Linux kernel中有哪些奇技淫巧

介绍static key的

130期 咱们提到过Bounded dynamicism with cross-modifying code

也是类似的玩意,也有现成的库提供,https://github.com/backtrace-labs/dynamic_flag

分享一个asio下使用channel来实现无需队列的安全的连续async_write的方法

不明觉厉

Detecting multiple instantiations

利用statufull template来检查,算了吧,不看了

GDB反向调试:让程序逆序执行,代码调试原来这么简单!体验时光旅行的快感!

其实就是record录制

视频

C++ Weekly - Ep 412 - Possible Uses of C++23’s [[assume]] Attribute

可能的一种用法

#ifdef NDEBUG
#define assert(x) [[assume(x)]]
#else
#define assert(x) /* unreach, abort都行,自定义 */
#endif

感觉不如builtin_expect,暂时别用

Data Storage in Entity Component Systems - Mathieu Ropert - Meeting C++ 2023

讲ecs框架和entt的

讲entt的视频,最近我看到一个不错的 b站 BV1X841127Rq

Regular, revisited - Victor Ciura - Meeting C++ 2023

讲value的。听困了

开源项目介绍

热门库最近更新了什么

主要是代码规范化 比如 https://github.com/scylladb/seastar/pull/2054

@@ -224,11 +225,7 @@ template <typename T, size_t Capacity>
 inline
 circular_buffer_fixed_capacity<T, Capacity>::circular_buffer_fixed_capacity(circular_buffer_fixed_capacity&& x) noexcept
         : _begin(x._begin), _end(x._end) {
-    // This is std::uninitialized_move, but that is c++17 only
-    auto dest = begin();
-    for (auto& obj : x) {
-        new (&*dest++) T(std::move(obj));
-    }
+    std::uninitialized_move(x.begin(), x.end(), begin());
 }

另外就是修复bug,延长请求,请求没结束不释放handler https://github.com/scylladb/seastar/pull/2044/

folly类似seastar 把版本切17之后做了很多的适配和bugfix

比较好玩的是folly::tape 类似vector<vector> 但性能更好,常见场景就是 vector<vector<char>>

https://github.com/facebook/folly/pull/2109/

在使用场景上做了取舍

实现思路就是拍扁,一维,比如tape<vector<char>> 就是vector<char>, 记录所有元素的index和offset

没有SSO优化的话,这种形态比vector<vector<char>>局部性要好

感兴趣大家可以以及看一看

改动非常多,一月改动如下

还是有很多可以学习的地方都

另外rocksdb我也会更新,这个更新的内容不会放在这里,会单独发

也会放在 https://wanghenshui.github.io/rocksdb-doc-cn 里

互动环节

寒冬了兄弟们。最近在群里聊天探讨活路,感叹数据库行业之难活,dead business。

贴近业务才能活,但业务有时候都活不下去。大家都在瞎几把忙

另外就是知识的诅咒吧,沟通尽量实事求是 有可能信息不对等大家不同

所以说读者读到不懂的地方,一定要评论,喷我,不对话交换共识,没有进步

另外最近的每日一题有点难,啥也不是,散会!


本文永久链接

看到这里或许你有建议或者疑问或者指出错误,请留言评论! 多谢! 你的评论非常重要!也可以帮忙点赞收藏转发!多谢支持! 觉得写的不错那就给点吧, 在线乞讨 微信转账