VS中 配置项 MTD_StaticDebug和MTD_DynamicDebug有什么区别?

已阅读 10 个网页

MTd_StaticDebug 和 MDd_DynamicDebug 是 Visual Studio 中控制C/C++ 运行时库(CRT,C Runtime Library)链接方式的两种不同配置选项。

简单来说,这两者的核心区别在于如何将 C++ 标准库的代码整合到你的程序中

  • MTd_StaticDebug (静态链接):直接将运行时库的代码复制并嵌入到你的最终程序 (.exe 或 .dll) 中。

  • MDd_DynamicDebug (动态链接):你的程序本身不包含运行时库的代码,而是在运行时去调用系统目录下独立的 .dll 文件(如 MSVCRTxxD.dll)。

两者名称中的 d (Debug) 都表示这是调试版本,包含了额外的调试信息和检查机制,因此体积更大,但便于开发时排查错误。

为了更清晰地展示它们的区别,可以参考下表:

对比维度 MTd_StaticDebug (静态) MDd_DynamicDebug (动态)
对应编译开关 /MTd  /MDd 
链接的库文件 LIBCMTD.lib  MSVCRTD.lib 
运行时依赖 无需额外依赖,自包含 需要目标机器存在特定版本的MSVCRTxxD.dll 
最终文件体积 较大(CRT 代码被静态打包进去) 较小(CRT 代码在共享的 DLL 中) 
内存管理/堆 每个模块(如多个静态库)有自己独立的堆,跨模块释放内存易出问题  所有模块共享同一个堆,内存管理更安全,适合复杂项目 
典型用途建议 简单的、不想依赖外部环境的独立小工具 多模块的大型项目、DLL(动态链接库)项目等 

⚠️ 关键规则:必须保持全局统一

在使用 Visual Studio 进行项目配置时,有一条绝对不能违反的铁律:一个进程(或最终的二进制文件)中,所有编译单元(包括你写的代码、引用的第三方静态库等)必须使用完全相同的运行时库设置

如果违反了此规则,最直观的表现就是在编译链接时遇到 LNK2038 错误,提示类似"value 'MTd_StaticDebug' doesn't match value 'MDd_DynamicDebug'"。

错误示例与排查思路

当你尝试将一个使用 /MTd 编译的静态库,链接到一个使用 /MDd 编译的主程序时,就会触发这类错误。

  • 错误场景一:项目设置与依赖库不匹配
    假设你的主程序设置为 /MDd (DynamicDebug),但链接到了一个使用 /MTd (StaticDebug) 编译的 third_party.lib,链接器就会报错。

  • 错误场景二:第三方库自身不统一
    这也是常见情况,比如你使用的某个 SDK(如 CEF、gRPC 等)或者通过 vcpkg、CMake 构建的库(如 Google Test、Protobuf、OpenCV 等),其默认编译方式可能与你的主项目不同。

解决方法:你需要确保整个解决方案中的所有项目(.exe 和它依赖的所有 .lib)的配置完全一致。要么全部改为 /MTd,要么全部改为 /MDd

💎 总结与建议

  • 检查当前配置:在 VS 中,右键项目 → 属性 → 配置属性 → C/C++ → 代码生成 → 运行库,即可查看或修改此选项。

  • 如何选择

    • 对于大型项目生成 DLL 的项目,默认的 /MDd 是更标准、更安全的选择。

    • 如果你需要生成一个独立的、不依赖任何 VC 运行库的 .exe 文件,可以考虑使用 /MTd

  • 遇到 LNK2038 错误时:这意味着统一性被破坏。你需要找到项目中配置不一致的模块,将其统一修改。如果你在编译某个开源库时遇到此错误,通常可以在其 CMake 配置中找到类似 gtest_force_shared_crt  或 protobuf_MSVC_STATIC_RUNTIME  的选项来匹配你的主项目配置。

那我一个程序中exe用动态链接,下面的dll用静态链接应该没问题吧?我也没办法保证我依赖的所有第三方dll都用统一的链接方式。

这是一个非常典型且容易让人困惑的问题。我来直接给出结论,然后详细解释原因。

简短结论:这是严重问题,会导致难以调试的内存崩溃

你的设想 EXE 用 /MDd (动态),DLL 用 /MTd (静态) 是不安全的,绝对不能这样做。

核心问题:堆(Heap)的不匹配

发生了什么?

  1. EXE (使用 /MDd):程序启动时,动态加载 MSVCRTxxD.dll,这个 DLL 会创建并管理一个全局堆。EXE 中所有的 newmalloc 都会从这个堆分配内存。

  2. DLL (使用 /MTd):编译时,静态链接版本的 CRT 代码被直接嵌入到你的 DLL 文件中。这个静态版本的 CRT 会创建自己独立的堆(完全独立于 EXE 的堆)。

  3. 灾难现场

    cpp

    // 在 DLL 内部(使用了 /MTd)
    void* ptr = malloc(100);  // 从 DLL 的私有堆分配
    
    // 在 EXE 内部(使用了 /MDd)
    delete ptr;  // EXE 试图将 ptr 释放到 EXE 的堆中!
    // 崩溃!或者产生内存损坏!

为什么会这样?能否规避?

你可能会想:"我不在 EXE 中释放 DLL 分配的内存不就行了?"

危险的现实

即使你小心翼翼地避免跨模块释放内存,问题依然存在:

  1. C++ 对象的隐式操作

    cpp

    // DLL 中(/MTd)
    class MyClass {
        std::vector<int> data;  // vector 内部会分配/释放内存
    };
    
    MyClass* create() { return new MyClass(); }
    
    // EXE 中(/MDd)
    MyClass* obj = create();
    delete obj;  // 崩溃!析构函数释放 vector 的内存时出错
  2. 异常跨模块传递
    如果 DLL 抛出异常,EXE 去捕获它,需要跨越不同 CRT 版本的异常处理机制,这几乎必然崩溃。

  3. 标准库对象的跨模块使用

    cpp

    // DLL 返回一个 std::string(/MTd)
    std::string getString();
    
    // EXE 使用这个 string(/MDd)
    std::string s = getString();  // 拷贝构造时可能崩溃

正确做法:必须统一

方案一:全部使用动态链接(/MDd)✅ 强烈推荐

text

EXE (/MDd) ← DLL1 (/MDd) ← DLL2 (/MDd)
         ← 第三方DLL (官方提供的,通常是/MD)

这是 Windows 世界的标准实践,原因:

  • 所有模块共享同一个堆

  • 只有一个 CRT 副本在内存中,节省空间

  • 第三方库(Qt、OpenCV、FFmpeg 等)官方提供的预编译版本几乎都是 /MD

方案二:全部使用静态链接(/MTd)✅ 可行但有限制

text

EXE (/MTd) + DLL1 (/MTd) + DLL2 (/MTd) = 一个巨大的 EXE

问题

  • 如果静态链接到多个 DLL,每个 DLL 都有独立的 CRT 副本,仍有堆问题

  • 但至少可以控制:如果所有模块都用 /MTd,且你不跨模块传递内存/对象(只传递原始数据如 char*、int、struct),可以勉强工作

实际工作中的解决方案

场景1:你的项目 + 几个第三方DLL

最佳实践:全部使用 /MDd(Debug)//MD(Release)

如果某个第三方库只提供了静态库(.lib),可以:

  • 检查该库的配置,看能否重新编译为 /MDd

  • 或者联系供应商获取动态链接版本

场景2:你无法控制某些DLL的编译方式

核心原则:不要让这些不一致的模块互相传递C++对象!

cpp

// 错误做法:跨模块传递对象
DLL_MTD_API std::vector<int> getData();  // 危险!

// 正确做法:使用纯C接口
DLL_MTD_API void getData(int** data, int* size);  // 安全
DLL_MTD_API void freeData(int* data);  // 让分配者也负责释放

场景3:被迫混合使用时的救急方案

如果你实在无法统一,必须混合:

  1. 使用纯C接口隔离

    cpp

    // DLL 只导出 C 函数
    extern "C" {
        __declspec(dllexport) void* create_buffer(int size);
        __declspec(dllexport) void destroy_buffer(void* buf);
    }
  2. 分配和释放必须在同一模块内

  3. 永远不要跨模块传递 C++ 对象(std::string、vector、自定义类等)

  4. 不要跨模块抛出异常

调试 LNK2038 错误的实用技巧

当你遇到链接错误时:

  1. 查找哪个库配置不一致

    bash

    # 使用 dumpbin 检查 .lib 文件
    dumpbin /directives your_library.lib | findstr "DEFAULTLIB"
  2. 常见第三方库的配置选项

    • Google Testgtest_force_shared_crt CMake 选项

    • Protobufprotobuf_MSVC_STATIC_RUNTIME CMake 选项

    • OpenCVBUILD_WITH_STATIC_CRT CMake 选项

    • vcpkg:安装时选择 triplet,如 x64-windows(动态)vs x64-windows-static(静态)

总结

配置 EXE DLL 是否安全
/MDd /MDd 安全,标准做法
/MTd /MTd 安全(但不跨模块传递对象)
/MDd /MTd 危险!内存崩溃
/MTd /MDd 危险!内存崩溃

最终建议:坚持使用 /MDd(Debug)和 /MD(Release)。这是 Windows 上 99% 项目的标准,也是你遇到的所有第三方库期望的配置。如果必须静态链接,确保整个解决方案全部统一为 /MTd

那如果exe依赖的两个dll,一个是vs2019编译的,一个是vs2017编译的,这样两个依赖的运行时库也不一样,这样不会有问题吗?

已阅读 6 个网页

这是一个很好的追问,也是实际开发中经常遇到的场景。

直接回答你的问题:在大多数情况下,这样做并不会有问题,但前提是你必须满足一个关键条件。

✅ 核心结论:VS 2019 和 VS 2017 是二进制兼容的

从 Visual Studio 2015 Update 3 开始,一直到 VS 2017、VS 2019、VS 2022 乃至未来的 VS 2026,所有这些版本编译出来的 C++ 代码(包括运行时库)都是二进制兼容 (binary-compatible) 的。

这意味着,用 VS 2017 编译的 DLL,可以被 VS 2019 编译的 EXE 安全地调用;反之亦然。微软这么做的目的是让开发者可以升级工具链,而不用被迫重新编译所有依赖的第三方库。

它们兼容的根本原因是,虽然这些 Visual Studio 的版本号看起来变化很大,但它们底层使用的主要工具集版本号都是 14(VS 2015 是 v140,VS 2017 是 v141,VS 2019 是 v142,VS 2022 是 v143)。微软保证了这个主版本号内的所有版本是兼容的。

⚠️ 必须满足的关键条件:使用动态链接 (/MD)

这个“二进制兼容”能成立,有一个非常重要的前提:你提到的这个情况,和之前讨论的“/MDd vs /MTd”是同一个前提,即所有模块都必须动态链接到运行时库(使用 /MD 或 /MDd

在这个前提下,你的 EXE 和两个 DLL(一个 VS 2017 编译,一个 VS 2019 编译)都会链接到同一个动态链接库文件,比如 vcruntime140.dll 或 vcruntime140d.dll(Debug 版本)。

当程序运行时,最终被加载到进程中的只有一份 vcruntime140.dll。既然大家用的都是同一份运行时库代码,自然就在同一个“堆”上工作,也可以安全地跨模块传递 std::stringvector 等 C++ 对象,而不会出现内存问题。分发程序时,只需要确保目标机器安装了最新版的 Microsoft Visual C++ Redistributable 即可。

🚫 两个重要的例外情况(需要警惕)

当然,除了上述主要规则外,在两种特殊情况下依然可能出问题,需要你留心检查:

  1. 使用了“全程序优化 (/GL)”或“链接时代码生成 (/LTCG)”
    如果你的某个 DLL 项目开启了 /GL 编译选项,并使用 /LTCG 进行链接,那么这个 DLL 的二进制文件就与不同版本的工具集不兼容了。

    • 示例:一个用 VS 2019 v16.7 工具集、开启了 /GL 选项编译的静态库,无法在 VS 2019 v16.8 工具集上链接。

    • 解决方法:检查所有相关项目的“C/C++ -> 优化”和“链接器 -> 优化”设置。如果开启了这些选项,你需要确保整个程序的所有模块(EXE 和 DLL)都是用完全相同的编译器版本(甚至包括具体的 Update 版本)构建的。

  2. 跨过“2015”这个边界
    上述“二进制兼容”的承诺是从 VS 2015 Update 3 开始的。如果你依赖的第三方 DLL 是使用 VS 2013 或更早版本的工具集编译的,那么它与你 VS 2019/2017 编译的 EXE 不兼容

    • 解决方案:必须获取该 DLL 的源代码,并用 VS 2017 或 2019 重新编译它;或者放弃使用它。

💎 总结

你描述的情况“EXE 用 VS 2019,DLL 用 VS 2017”是安全且被官方支持的,前提是:

  1. 两者都使用动态链接运行时库 (/MD 或 /MDd)

  2. 没有使用 /GL 和 /LTCG 选项进行全程序优化

  3. 它们不是 VS 2013 或更早版本编译的

微软设计这套兼容性,就是为了让你这种情况能正常工作,避免“DLL Hell”。

VS2015 以后所有的C++运行时库名字都是vcruntime140d.dll吗?

已阅读 3 个网页

不是的,这是一个常见的误区。

实际上,Visual Studio 2015 及之后版本的 C++ 运行时库不是一个单一的 DLL,而是被拆分成了两个核心部分vcruntime140.dll 和 ucrtbase.dll

1. 为什么不是只有一个 DLL?

在 Visual Studio 2015 中,微软对 C++ 运行时库进行了一次重大重构。旧版本(VS2013及之前)中那个庞大的 msvcr120.dll 被拆分成了两部分:

  • vcruntime140.dll:包含 C++ 特定的东西,如程序启动和异常处理。

  • ucrtbase.dll:包含标准的 C 库函数,如 printfmalloc 等,现在是 Windows 系统组件。

2. 版本号 “140” 的含义

文件名中的 140 代表的是 工具集版本号 (Toolset Version),而不是 Visual Studio 的发布年份。

Visual Studio 版本 工具集版本 对应的 DLL 名称
VS 2015 v14.0 vcruntime140.dll
VS 2017 v14.1 vcruntime140.dll
VS 2019 v14.2 vcruntime140.dll
VS 2022 v14.3 vcruntime140.dll

如表格所示,从 VS 2015 到 VS 2022,虽然年份变了,但 DLL 的文件名都叫 vcruntime140.dll。这就是为什么之前提到它们二进制兼容的原因——本质上它们都是基于 v14 这个主干迭代的。

3. 关于 “vcruntime140d.dll” 的特殊性

你问题中提到的 vcruntime140d.dll 确实是调试版本。文件名中的 d 代表 Debug。

  • 发布版 (Release)vcruntime140.dll (可再分发,可通过 VC_redist.exe 安装)。

  • 调试版 (Debug)vcruntime140d.dll (不可再分发,仅存在于开发机的 Visual Studio 安装目录下,用于调试)。

总结

下次查看文件时,可以这样区分:

  • VS2015/2017/2019/2022 编译的程序:依赖 vcruntime140.dll

  • VS2013 编译的程序:依赖 msvcr120.dll

  • 文件名带 d:是调试版,不要发给用户。

如果我的exe和所有的dll都是静态链接c++运行时库,那我最终运行的进程会包含多个运行时库吗?会包含多个全局堆吗?

这是一个非常好的问题,直指静态链接的核心技术细节。

直接给出结论:是的,你的进程会包含多份C++运行时库的代码副本,并且会拥有多个独立的全局堆。

下面来详细解释这会导致什么后果。

1. 会发生什么?

假设你有一个main.exe和两个DLL(A.dllB.dll),并且全部都使用/MTd(静态链接)编译。

  • 代码层面:最终生成的main.exeA.dllB.dll这三个二进制文件,各自都独立包含了一份完整的C++运行时库代码。比如,mallocfreenewdeletestd::string的实现代码,会被分别复制到这三个文件里。

  • 内存堆层面:这是最关键的问题。程序启动时:

    1. main.exe启动,它内部的静态CRT代码会执行初始化,在进程的默认堆之外,创建一个私有的全局堆(称为堆A)。

    2. LoadLibrary加载A.dll时,该DLL内的静态CRT代码会再次执行初始化。因为它是一个独立的代码副本,它不知道堆A的存在,所以它也会创建另一个私有的全局堆(称为堆B)。

    3. 同样地,B.dll会创建第三个私有全局堆(称为堆C)。

最终,你的一个进程里,有三个功能完全一样、但互相隔离的堆

2. 这会导致什么问题?

最直接的后果:跨模块内存释放导致崩溃

这是静态链接最致命的问题。

cpp

// 在 A.dll 的代码中 (静态链接 /MT)
int* getData() {
    int* p = new int[100]; // 内存从 堆B (A.dll的堆) 分配
    return p;
}

// 在 main.exe 的代码中 (静态链接 /MT)
void test() {
    int* data = getData(); // 拿到指针
    delete[] data; // 释放内存 -> main.exe 试图将指针释放到 堆A
    // 💥 崩溃!因为释放内存的堆 (堆A) 不是分配内存的堆 (堆B)
}
复杂的后果:资源泄漏与标准库对象失效
  1. 资源泄漏:即便你的代码从不跨模块delete,只要使用了某些C++特性,就会出问题。

    cpp

    // A.dll 返回一个 std::string
    std::string getName() {
        std::string s = "hello";
        return s; // s 的内存在 堆B 上分配
    }
    
    // main.exe 接收并销毁它
    std::string name = getName(); 
    // 当 name 离开作用域时,main.exe 调用 std::string 的析构函数
    // 这个析构函数会尝试释放 堆B 上的内存,但运行它的代码属于 main.exe (使用堆A)
    // 💥 崩溃或内存损坏

    任何在A.dll中创建、在main.exe中销毁的std::stringstd::vector等容器都会导致崩溃。

  2. 符号冲突(很难调试的bug):
    如果main.exeA.dll都定义了一个全局变量(比如std::cout的某种内部状态),它们会各自拥有独立的副本。这会导致一些依赖全局状态的行为产生不符合预期的结果。

3. 什么情况下静态链接没问题?

虽然听起来很可怕,但在受控条件下,静态链接是可以工作的。工程实践中,也有很多大型项目使用/MT

核心原则:所有跨模块边界的接口,必须是“纯C接口”或纯粹的Windows API,并且遵循“谁分配,谁释放”的规则。

安全的做法示例:

cpp

// A.dll 的公共头文件 (纯C接口)
#ifdef __cplusplus
extern "C" {
#endif

    // 规则1: 只传递原始指针和基本类型
    void* create_buffer(size_t size);
    
    // 规则2: 同一个模块负责释放自己创建的资源
    void destroy_buffer(void* buffer);
    
    // 规则3: 让调用者提供缓冲区,而不是返回新对象
    void get_data(void* user_provided_buffer, size_t buffer_size);

#ifdef __cplusplus
}
#endif

实施原则

  • DLL内部可以用满C++特性,尽情使用std::vectorstd::string

  • 但在DLL的边界(导出的函数参数和返回值),只能使用intchar*void*struct*这些C语言类型。

  • DLL内部创建的资源(如对象的指针),必须由DLL自身提供销毁函数。

总结

配置方案 运行时库副本数 全局堆数量 跨模块传递C++对象 推荐度
全部动态链接(/MD) 1份 (系统DLL) 1个 (共享) ✅ 安全 ⭐⭐⭐⭐⭐ (首选)
全部静态链接(/MT) N份 (每个模块一份) N个 (隔离) ❌ 崩溃 ⭐⭐ (除非必须)
  • 首选方案:始终优先使用动态链接(/MD)。这符合Windows的设计理念,避免了这些棘手的内存问题。

  • 静态链接(/MT) 只应在以下严格条件下使用:

    • 完全控制所有模块的接口,并能确保遵循“纯C接口”约定。

    • 你需要生成一个独立、无额外依赖的单一.exe文件(不适用于包含DLL的场景)。

    • 你需要解决DLL部署时的复杂依赖问题(通常引入的内存管理问题比解决部署问题更麻烦)。

针对你最初设想的“EXE和所有DLL都用静态链接”方案:如果项目只有EXE,没有DLL,是可以的。但如果项目由EXE和多个DLL组成,那么这个方案会埋下巨大的隐患。除非你极其严格地遵守“纯C接口”原则,否则几乎一定会遇到难以调试的随机崩溃。

那如果我有几个静态库是静态链接c++运行时库,最后把几个静态库一起链接成exe的时候也是静态链接c++运行时库,此时exe里面会包含多个c++运行时库吗?

这是一个非常精彩的追问,它精准地触及了静态链接静态库工作的核心原理。

直接给出结论:不会。 最终生成的 EXE 里只有一份 C++ 运行时库的代码。

这是一个和“多个 DLL 各自包含一份”完全不同的情况,也是静态链接工作方式的精髓所在。

为什么“多个静态库”链接成 EXE 时不会有多份?

关键在于理解静态库 (.lib) 和动态链接库 (.dll) 的本质区别:

  • 静态库 (.lib):它不是可执行代码,而是一个代码的“压缩包”或“归档文件”。里面存放着编译好的、但还没有链接到最终程序里的 .obj 目标文件。

  • 链接过程:当你把几个静态库链接成一个 EXE 时,链接器的工作是解开这些压缩包,把里面的 .obj 文件拿出来,然后像玩拼图一样,把这些 .obj 和你主程序的 .obj 合并成一个完整的、连贯的 EXE 文件。

具体会发生什么?

假设你有 LibA.lib 和 LibB.lib,它们都是使用 /MT (静态链接) 编译的。

  1. 编译阶段

    • 编译 LibA 时,它所需的 C++ 运行时函数(比如 malloc 的代码),并没有被放进 LibA.lib 里。

    • 放进 LibA.lib 的只是一个符号引用,就像一个“欠条”或“空位”,上面写着:“我需要一个叫 malloc 的函数,但我现在还没拿到它的代码,等最后链接的时候请把它的代码填到这个空位来”。

  2. 链接阶段

    • 当你把 LibA.libLibB.lib 和你的 main.obj 链接成 myapp.exe 时,链接器看到了所有这些“欠条”。

    • 链接器的标准库路径里放着真正的 libcmt.lib (静态CRT库的真实代码库)。

    • 链接器会从 libcmt.lib 中,精确地取出一份 mallocfreestd::string 等函数的代码,然后把所有 LibALibB 和 main.obj 中的“欠条”都指向这同一份代码。

  3. 最终结果

    • 最终的 myapp.exe 里,只包含了一份从 libcmt.lib 里提取出来的 C++ 运行时库的机器码。

    • 全局堆也只有一个LibALibB 和 main.exe 中的代码都使用这同一个堆来分配和释放内存。

与“多个 DLL 静态链接”的对比

用一张表格来清晰对比这两种看似相似、实则天差地别的情况:

场景 链接方式 最终产物 运行时库副本数 全局堆数量
场景A (你之前的担心) 多个 DLL,各自静态链接(/MT) A.dllB.dllmain.exe 3份 (每个文件里都有一份) 3个 (运行时隔离)
场景B (你现在的问题) 多个 静态库,最终 EXE 静态链接(/MT) myapp.exe 1份 (只有 EXE 里有) 1个 (完全共享)

为什么会有这种差异?

  • DLL 的边界是“高墙”:DLL 是一个独立的二进制模块,有自己独立的地址空间和初始化入口 (DllMain)。链接器无法跨越 DLL 边界去“合并”代码。因此,每个静态链接的 DLL 都必须打包一份自己的运行时库,并拥有自己独立的堆。

  • 静态库的边界是“虚线”:静态库在链接前只是一堆文件。链接器可以随意将它们打散、合并、优化,最终融合成一个单一的二进制文件。

总结与最终建议

  1. 如果你把多个使用 /MT 的静态库链接成一个使用 /MT 的 EXE:

    • 完全安全。最终只有一个堆,可以放心地跨库(实际上是跨 .obj 文件)分配和释放内存,也可以自由地传递 std::stringstd::vector 等 C++ 对象。

  2. 回顾一下你之前的两个问题:

    • 问题1exe (/MDd) + dll (/MTd) -> 危险

    • 问题2exe (/MTd) + dll1 (/MTd) + dll2 (/MTd) -> 危险

    • 问题3exe (/MTd) + 静态库A (/MTd) + 静态库B (/MTd) -> 安全

这个对比清晰地揭示了 Windows 下 C++ 项目配置的核心原则:问题几乎总是出在“可执行文件的边界”上

当你跨越一个 .exe 或 .dll 的边界去管理内存或传递 C++ 对象时,必须确保两边使用的是完全相同的一份运行时库代码(通常意味着全部动态链接 /MD)。而如果你把所有的代码(无论有多少个静态库)最终都“包”进同一个 .exe 或 同一个 .dll 文件内部,那么静态链接 /MT 就是一个简单、安全、自包含的选择。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐