最新帖子 精华区 社区服务 会员列表 统计排行
主题 : Windows核心编程——读书笔记
updowndown 离线
级别: 菜鸟
UID: 1211
精华: 0
发帖: 38
金币: 0 个
银元: 13 个
铜钱: 198 个
技术分: 0 个
在线时间: 16(时)
注册时间: 2011-01-17
最后登录: 2013-03-14
楼主  发表于: 2011-03-31   

Windows核心编程——读书笔记

管理提醒: 本帖被 admin 从 阶段1讨论区 移动到本区(2016-11-20)
第一章

Windows函数常用返回值类型及有效判定

返回类型   表示失败的值
VOID    该函数不可能失败
BOOL    成功时返回非0值,失败返回0,判断返回是否为0,不要测试返回值是否为TRUE
HANDLE   失败时返回NULL或INVALID_HANDLE_VALUE
PVOID    失败返回NULL
LONG/DWORD 具体查看函数的说明文档

/// 错误码以及错误描述
#include "stdafx.h"
#include "windows.h"
#include <iostream>
using namespace std;

void CallErrorCode()
{
    /// 设置ErrorCode,这样在下一个错误发生前,通过GetLastError得到的就是
    /// 我们设置的错误了
    SetLastError(ERROR_TOO_MANY_LINKS);
}

void PrintErrorDiscription( DWORD dwErrorCode )
{
    HLOCAL hlocal = NULL;

    /// 获取错误码对应的错误描述
    /// FORMAT_MESSAGE_FROM_SYSTEM 使用系统错误码
    /// FORMAT_MESSAGE_ALLOCATE_BUFFER 分配描述字符串内存
    /// MAKELANGID( LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED) 指定使用简体中文描述错误
    BOOL bOK = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
                NULL, dwErrorCode, MAKELANGID( LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED),
                (LPSTR)&hlocal, 0, NULL);

    if( bOK == FALSE || hlocal == NULL )
    {
        cout << "寻找错误码描述失败" << endl;
        return;
    }

    cout << dwErrorCode << ": " << (PCTSTR)LocalLock(hlocal);
}

int _tmain(int argc, _TCHAR* argv[])
{
    HANDLE hfile = CreateFile("\\Test", 0, 0, NULL, OPEN_EXISTING, 0, NULL);

    DWORD dwErrorCode;
    /// 获取最近的一次错误
    dwErrorCode = GetLastError();
    PrintErrorDiscription(dwErrorCode);

    CallErrorCode();
    dwErrorCode = GetLastError();
    PrintErrorDiscription(dwErrorCode);

    return 0;
}
updowndown 离线
级别: 菜鸟
UID: 1211
精华: 0
发帖: 38
金币: 0 个
银元: 13 个
铜钱: 198 个
技术分: 0 个
在线时间: 16(时)
注册时间: 2011-01-17
最后登录: 2013-03-14
沙发  发表于: 2011-03-31   
第三章

内核对象:
    内核对象时内核所分配的一个内存块,系统和应用程序可以通过它来管理各种各样的资源,如进程、线程、文件等。
    内核对象只能由内核来访问。应用程序无法在内存中找到这些数据结构及直接改变它们的内容。
    内核对象与进程密切相关。

句柄:
    用于标示内核对象的值。通过创建或打开内核对象的函数返回。
    应用程序通过句柄以及特定的函数来访问内核对象。
    句柄不能跨进程访问。

内核对象与其它对象的区别:
    创建内核对象的函数基本上都有安全性参数
    PSECURITY_ATTRIBUTES

句柄有效性判断:
    内核对象创建失败时,通常返回NULL(0),但也有可能返回INVALID_HANDLE_VALUE(-1),要仔细查看函数的说明文档。

跨越进程边界共享内核对象
    1、使用对象句柄的继承性
        只有当进程具有父子关系时,可以使用此方法。
        ①创建内核对象时,声明可继承,例
            /// 安全属性
            SECURITY_ATTRIBUTES sa;
            sa.nLength = sizeof(sa);
            sa.lpSecurityDescriptor = NULL;
            /// 是否可继承
            sa.bInheritHandle = TRUE;

            HANDLE hMutex = CreateMutex(&sa, FALSE, NULL);
            
        ②创建子进程时,声明继承父进程的内核对象,通过制定CreateProcess中bInheritHandle为True实现
            
    2、命名对象
        创建对象时,给对象命名。不同进程间可通过名称来访问同一对象。
        Create*函数创建对象时,若已存在同名对象,返回该对象句柄,LastError设为ERRO_ALREADY_EXISTS
        Open*函数打开对象时,存在则返回句柄,否则返回NULL
            
    3、通过DuplicateHandle复制句柄
admin 离线
级别: 管理员
UID: 1
精华: 1
发帖: 997
金币: 526 个
银元: 488 个
铜钱: 7853 个
技术分: 601 个
在线时间: 736(时)
注册时间: 2010-04-21
最后登录: 2018-04-20
板凳  发表于: 2011-03-31   
好啊,学习了
updowndown 离线
级别: 菜鸟
UID: 1211
精华: 0
发帖: 38
金币: 0 个
银元: 13 个
铜钱: 198 个
技术分: 0 个
在线时间: 16(时)
注册时间: 2011-01-17
最后登录: 2013-03-14
地板  发表于: 2011-04-01   
第四章

进程:
    进程是一个正在运行的程序的实例。进程不执行各种实际操作。实际操作由进程中的线程执行。
    每一个进程至少拥有一个线程,这个线程就是它的主线程。
    
HMODULE与HINSTANCE是完全相同的对象。

可以通过
HMODULE GetModuleHandle(PCTSTR pszModule)
函数获取加载到进程空间中可执行文件或Dll的基地址,即句柄。当pszModule为NULL,返回进程的基址。

终止进程的运行
    终止进程有4种方法
    ①主线程进入点函数返回
    ②调用ExitProcess函数(本进程强制终结)
    ③调用TeminateProcess函数(可强制终结其它进程)
    ④进程中所有线程终止运行
    除特殊情况发生,否则最好使用第一种方法
updowndown 离线
级别: 菜鸟
UID: 1211
精华: 0
发帖: 38
金币: 0 个
银元: 13 个
铜钱: 198 个
技术分: 0 个
在线时间: 16(时)
注册时间: 2011-01-17
最后登录: 2013-03-14
4楼  发表于: 2011-04-01   
关于进程的一些函数使用实例

#include "stdafx.h"
#include <Windows.h>
#include <iostream>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    /// 获取完整命令行
    PTSTR pszCmdLine;
    pszCmdLine = GetCommandLine();
    cout << pszCmdLine << endl;

    /// 分割命令行为单独的各个参数,Unicode版
    int nNumArgs;
    PWSTR *ppArgv = CommandLineToArgvW( GetCommandLineW(), &nNumArgs );
    for(int i = 0; i < nNumArgs; ++i)
    {
        WCHAR* pszArg = ppArgv;
        wcout << pszArg << endl;
    }
    /// 分配的内存不需要释放,若一定要释放,需要通过这种方式进行
    HeapFree(GetProcessHeap(), 0, ppArgv);

    /// 设置环境变量
    /// Test 环境变量名
    /// This is a test 环境变量值
    SetEnvironmentVariable(TEXT("Test"), TEXT("This is a test"));

    /// 取环境变量
    /// Test 环境变量名
    /// pszValue 输出缓冲区
    /// MAX_PATH 输出缓冲区大小
    TCHAR pszValue[MAX_PATH];
    GetEnvironmentVariable(TEXT("Test"), pszValue, MAX_PATH);
    cout << pszValue << endl;


    /// 获取程序当前目录
    /// MAX_PATH 缓冲区大小
    /// pszCurDir 缓冲区
    /// 返回读取到的字符串长度
    DWORD dwReadLenth;
    TCHAR pszCurDir[MAX_PATH];
    dwReadLenth = GetCurrentDirectory(MAX_PATH, pszCurDir);
    cout << "设置前路径: " << pszCurDir << endl;

    // 设置当前路径
    // 输入:路径
    // 返回:是否设置成功
    BOOL bOK = SetCurrentDirectory("C:\\");
    if( bOK != FALSE )
    {
        cout << "设置成功" << endl;
    }
    else
    {
        cout << "设置失败" << endl;
    }

    dwReadLenth = GetCurrentDirectory(MAX_PATH, pszCurDir);
    cout << "设置后路径: " << pszCurDir << endl;

    /// 获取指定驱动器的当前目录
    /// 可在环境变量中更改
    /// =C:=C:\Utility\Bin
    /// =D:=D:\Program Files;
    dwReadLenth = GetFullPathName(TEXT("D:"), MAX_PATH, pszCurDir, NULL);
    cout << pszCurDir << endl;

    SetEnvironmentVariable(TEXT("=D:"), TEXT("D:\\Program Files"));
    dwReadLenth = GetFullPathName(TEXT("D:"), MAX_PATH, pszCurDir, NULL);
    cout << pszCurDir << endl;

    /// 获取本机系统信息
    OSVERSIONINFOA VersionInfo = {sizeof(VersionInfo)};
    bOK = GetVersionEx(&VersionInfo);


    return 0;
}
updowndown 离线
级别: 菜鸟
UID: 1211
精华: 0
发帖: 38
金币: 0 个
银元: 13 个
铜钱: 198 个
技术分: 0 个
在线时间: 16(时)
注册时间: 2011-01-17
最后登录: 2013-03-14
5楼  发表于: 2011-04-01   
CreateProcess()函数的使用以及父子进程通信

父进程:

#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <Windows.h>
using namespace std;

/****************************************************************************
*    BOOL CreateProcess(
*    IN LPCSTR lpApplicationName,                    ///< 可执行文件名
*    IN LPSTR lpCommandLine,                            ///< 命令行字符串,应放在可读/写内存中
*    IN LPSECURITY_ATTRIBUTES lpProcessAttributes,   ///< 进程安全性
*    IN LPSECURITY_ATTRIBUTES lpThreadAttributes,    ///< 主线程安全性
*    IN BOOL bInheritHandles,                        ///< 是否继承父进程句柄
*    IN DWORD dwCreationFlags,                        ///< 标志,标记如何创建新进程
*    IN LPVOID lpEnvironment,                        ///< 环境字符串内存块
*    IN LPCSTR lpCurrentDirectory,                    ///< 进程当前驱动器和目录
*    IN LPSTARTUPINFOA lpStartupInfo,                ///< 启动信息
*    OUT LPPROCESS_INFORMATION lpProcessInformation    ///< 新建进程的信息
*    );
****************************************************************************/

int _tmain(int argc, _TCHAR* argv[])
{
    /// 通过环境块传递信息
    SetEnvironmentVariable(TEXT("Test"), TEXT("This is a test"));


    /// 创建子进程,不继承父进程句柄
    STARTUPINFO si = {sizeof(si)};
    PROCESS_INFORMATION piProcessInfo;
    BOOL bSuccess = CreateProcess(NULL, "4-2Test.exe", NULL, NULL, FALSE, 0, NULL, NULL, &si, &piProcessInfo);
    if( bSuccess != FALSE )
    {
        /// 若不使用对应句柄,应尽早释放,以避免资源泄漏,释放句柄只是代表本进程无法再对子进程进行操作
        /// 但子进程仍然在运行,直到运行结束或被终止
        CloseHandle(piProcessInfo.hThread);

        /// 等待子进程结束
        WaitForSingleObject(piProcessInfo.hProcess, INFINITE);

        /// 获取子进程结束码,非0表示成功结束,0表示失败,会设置GetLastError,若返回STILL_ACTIVE表示进程仍在运行
        DWORD dwExitCode;
        GetExitCodeThread(piProcessInfo.hProcess, &dwExitCode);

        cout << "子进程已结束" << endl;
        CloseHandle(piProcessInfo.hProcess);
    }

    /// 创建匿名事件,声明可继承
    SECURITY_ATTRIBUTES sa;
    sa.nLength = sizeof(sa);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;

    HANDLE hMutex = CreateMutex( &sa, TRUE, NULL);
    cout << "父线程句柄为: " << hMutex << endl;

    /// 通过文件传递句柄号
    ofstream file("Data.txt");
    if( file )
    {
        file << hMutex;
    }
    file.close();

    ReleaseMutex(hMutex);
    /// 创建进程,继承父进程句柄,
    /// 通过CreateProcess创建的进程与线程句柄也可声明为可继承,通过指定第3、第4个参数中的bInheritHandle为True来实现
    bSuccess = CreateProcess(NULL, "4-2Test.exe", NULL, NULL, TRUE, 0, NULL, NULL, &si, &piProcessInfo);
    CloseHandle(hMutex);
    CloseHandle(piProcessInfo.hThread);
    CloseHandle(piProcessInfo.hProcess);

    while( cin );

    return 0;
}


子进程:

#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <Windows.h>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    /// 子进程可以通过环境变量名访问父进程定义的环境变量
    TCHAR pszValue[MAX_PATH];
    DWORD dwLen = GetEnvironmentVariable(TEXT("Test"), pszValue, MAX_PATH);

    if( dwLen > 0 )
    {
        cout << pszValue << endl;
    }
    else
    {
        cout << "未找到" << endl;
    }

    HANDLE hMutex;
    ifstream file("Data.txt");
    if( file )
    {
        /// 读取父进程写入的句柄
        file >> hMutex;
        file.close();

        /// 判定该句柄是否有效
        DWORD dwResult = WaitForSingleObject(hMutex, INFINITE);
        if( dwResult == WAIT_OBJECT_0)
        {
            cout << "子线程句柄: " << hMutex << "有效" << endl;
        }
        else
        {
            cout << "子线程句柄: " << hMutex << "无效" << endl;
        }
    }
    CloseHandle(hMutex);
    return 0;
}
updowndown 离线
级别: 菜鸟
UID: 1211
精华: 0
发帖: 38
金币: 0 个
银元: 13 个
铜钱: 198 个
技术分: 0 个
在线时间: 16(时)
注册时间: 2011-01-17
最后登录: 2013-03-14
6楼  发表于: 2011-04-02   
对第五章无爱,直接略过。

第六章:

能使用线程实现的时候,尽量使用线程而不是进程。

创建线程的函数
    CreateThread:用于创建线程的Windows函数,当使用C/C++时,不要使用该函数
    _beginthread:C/C++运行库函数,绝不应该调用
    _beginthreadex:C/C++运行库函数,编写C/C++程序时,应调用此函数
    AfxBeginThread:Windows核心编程未提到此函数,但编写MFC程序时应使用此函数来创建线程

线程的终止
    同样有4种方法:
    ①线程函数返回
    ②线程通过调用ExitThread终止自己(自己调用)
    ③TerminateThread(本进程或其它进程的线程调用)
    ④包含线程的进程终止
    尽可能使用第一种方法以避免资源泄漏。

C/C++编写多线程程序时要注意运行时库的选择

updowndown 离线
级别: 菜鸟
UID: 1211
精华: 0
发帖: 38
金币: 0 个
银元: 13 个
铜钱: 198 个
技术分: 0 个
在线时间: 16(时)
注册时间: 2011-01-17
最后登录: 2013-03-14
7楼  发表于: 2011-04-02   
线程相关函数的使用:


#include "stdafx.h"
#include <iostream>
#include <Windows.h>
#include <process.h>
using namespace std;

/// 打印错误码,源于第一章
void PrintErrorDiscription( DWORD dwErrorCode )
{
    HLOCAL hlocal = NULL;

    BOOL bOK = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL, dwErrorCode, MAKELANGID( LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED),
        (LPSTR)&hlocal, 0, NULL);

    if( bOK == FALSE || hlocal == NULL )
    {
        cout << "寻找错误码描述失败" << endl;
        return;
    }

    cout << dwErrorCode << ": " << (PCTSTR)LocalLock(hlocal);
}

/// 关键代码段,用于防止在输出一行字符时被另一个线程插入
CRITICAL_SECTION g_section;

/// 计数线程,输出从0—49,线程函数都为指定格式
unsigned __stdcall CountThread( PVOID pvParm )
{
    for( int i = 0; i < 50; ++i )
    {
        /// 进入临界区,在离开临界区前,其它进入临界区的函数会一直等待
        EnterCriticalSection(&g_section);
        cout << "Thread:" << i << endl;
        LeaveCriticalSection(&g_section);

        /// Sleep以便主线程分配时间片
        Sleep(50);
    }

    return 0;
}

BOOL GetThreadTime( HANDLE hMainThread )
{
    FILETIME ftCreateTime, ftExitTime, ftKernelTime, ftUserTime;

    /// 获取线程相关时间
    BOOL bOK = GetThreadTimes(hMainThread, &ftCreateTime, &ftExitTime, &ftKernelTime, &ftUserTime);

    if( bOK != FALSE )
    {
        cout << "获取成功" << endl;
    }
    else
    {
        DWORD dwCode = GetLastError();
        PrintErrorDiscription(dwCode);
    }
    return bOK;
}

int _tmain(int argc, _TCHAR* argv[])
{
    /// 初始化临界区
    InitializeCriticalSection(&g_section);

    /// 启动计数线程,因为使用的是C++,所以选用_beginthreadex()
    HANDLE hThread = (HANDLE)_beginthreadex( NULL,    ///< 安全属性
                                    0,                ///< 指定线程栈的大小,0为默认
                                    CountThread,    ///< 启动线程函数名
                                    0,                ///< 给线程传递的参数,为void*,一般传递通过new操作得到的指针,已保证参数的生存期
                                    0,                ///< 创建标志,为0表示线程创建后直接运行,为CREATE_SUSPENDED时,创建后线程会挂起
                                                    ///< 通过ResumeThread函数唤醒
                                    NULL            ///< 线程返回的线程ID,为NULL时不存储线程ID
                                    );
    /// 主线程也开始计数
    for( int i = 0; i < 50; ++i )
    {
        EnterCriticalSection(&g_section);
        cout << "Main:" << i << endl;
        LeaveCriticalSection(&g_section);
        Sleep(50);
    }

    if( hThread != NULL )
    {
        /// 等待线程结束
        WaitForSingleObject(hThread, INFINITE);

        /// 等待完毕后,若不再需要hThread,应释放资源
        CloseHandle(hThread);
    }

    /// 删除临界区
    DeleteCriticalSection(&g_section);

    /// 获取当前线程的伪句柄,进程为GetCurrentProcess()
    /// 获取线程或进程ID,通过GetCurrentProcessID()与GetCurrentThreadID()
    HANDLE hCurThread = GetCurrentThread();

    /// 伪句柄在很多需要用到句柄的地方都会调用失败
    /// 但伪句柄不会增加引用计数,因此不需要CloseHandle()
    /// 如下,提示无效句柄
    GetThreadTime(hThread);

    /// 但可以通过复制句柄将伪句柄转换为真实句柄,也可用于进程句柄
    HANDLE hRealThread;
    DuplicateHandle( GetCurrentProcess(),    ///< 源进程句柄
                    hCurThread,                ///< 源线程句柄
                    GetCurrentProcess(),    ///< 目标进程句柄
                    &hRealThread,            ///< 转换后的线程句柄
                    0,                        ///< 访问控制,0表示与源线程句柄相同
                    FALSE,                    ///< 不可继承
                    DUPLICATE_SAME_ACCESS    ///< 表示与源进程有相同的访问控制
                    );

    /// OK,现在获取就成功了
    GetThreadTime(hRealThread);
    /// 转换句柄会增加引用计数,因此需要释放资源
    CloseHandle(hRealThread);
    return 0;
}
updowndown 离线
级别: 菜鸟
UID: 1211
精华: 0
发帖: 38
金币: 0 个
银元: 13 个
铜钱: 198 个
技术分: 0 个
在线时间: 16(时)
注册时间: 2011-01-17
最后登录: 2013-03-14
8楼  发表于: 2011-04-02   
第七章暂时用不到,也直接掠过
第八章:
互锁函数纪要以及使用,当不使用互锁函数时,输出结果明显不对。


#include "stdafx.h"
#include <iostream>
#include <Windows.h>
#include <process.h>
using namespace std;

/****************************************************************************
32位互锁函数,64位在函数名后加64

/// 递增,返回原始值
LONG InterlockedIncrement    
(
LONG volatile *lpAddend        ///< 递增值
);

/// 递增,返回原始值
LONG InterlockedDecrement
(        
LONG volatile *lpAddend        ///< 递减值
);

/// 将原值与指定值交换,返回原值
LONG InterlockedExchange
(            
IN OUT LONG volatile *Target,    ///< 原值
IN LONG Value                    ///< 交换值
);

/// 将原值与指定值交换,返回原值。指针版,可用于自定义对象,需删除原指针以避免资源泄漏。
PVOID InterlockedExchangePointer
(
IN OUT PVOID volatile *Target,    ///< 原值
IN PVOID Value                    ///< 交换值
);

/// 将原值增加指定值,返回原值
LONG InterlockedExchangeAdd
(
IN OUT LONG volatile *Addend,    ///< 原值
IN LONG Value                    ///< 增加值
);

/// 如果第三个参数与第一个参数指向的值相同,那么用第二个参数取代第一个参数指向的值。函数返回值为原始值。
LONG InterlockedCompareExchange
(
IN OUT LONG volatile *Destination,    ///< 原值
IN LONG ExChange,                    ///< 交换值
IN LONG Comperand                    ///< 比较值
);

/// 如果第三个参数与第一个参数指向的值相同,那么用第二个参数取代第一个参数指向的值。函数返回值为原始值。
/// 指针版,可用于自定义对象,需删除原指针以避免资源泄漏。
PVOID InterlockedCompareExchangePointer
(
IN OUT PVOID volatile *Destination,    ///< 原值
IN PVOID Exchange,                    ///< 交换值
IN PVOID Comperand                    ///< 比较值
);
****************************************************************************/

int g_AddTimes = 1000000;
int g_Int = 0;
long g_long = 0;
/// 实现g_AddTimes次自增
unsigned __stdcall AddThread( PVOID pvParm )
{
    
    for( int i = 0; i < 1000000; ++ i)
    {
        ++g_Int;
    }

    return 0;
}

/// 实现g_AddTimes次自增
unsigned __stdcall AddThread2( PVOID pvParm )
{
    for( int i = 0; i < 1000000; ++ i)
    {
        InterlockedIncrement(&g_long);
    }

    return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
    cout << "两个线程各递增" << g_AddTimes << "次" << endl;

    HANDLE hThread1 = (HANDLE)_beginthreadex( NULL, 0, AddThread, NULL, 0, NULL);
    HANDLE hThread2 = (HANDLE)_beginthreadex( NULL, 0, AddThread, NULL, 0, NULL);

    if( hThread1 != NULL )
    {
        WaitForSingleObject(hThread1, INFINITE);
        CloseHandle(hThread1);
        hThread1 = NULL;
    }

    if( hThread2 != NULL )
    {
        WaitForSingleObject(hThread2, INFINITE);
        CloseHandle(hThread2);
        hThread2 = NULL;
    }

    cout << "不使用互锁函数:" << g_Int << endl;

    hThread1 = (HANDLE)_beginthreadex( NULL, 0, AddThread2, NULL, 0, NULL);
    hThread2 = (HANDLE)_beginthreadex( NULL, 0, AddThread2, NULL, 0, NULL);

    if( hThread1 != NULL )
    {
        WaitForSingleObject(hThread1, INFINITE);
        CloseHandle(hThread1);
        hThread1 = NULL;
    }

    if( hThread2 != NULL )
    {
        WaitForSingleObject(hThread2, INFINITE);
        CloseHandle(hThread2);
        hThread2 = NULL;
    }
    
    cout <<  "使用互锁函数:" << g_long << endl;

    return 0;
}
m歌 离线
级别: 菜鸟
UID: 1456
精华: 0
发帖: 88
金币: 0 个
银元: 22 个
铜钱: 440 个
技术分: 0 个
在线时间: 33(时)
注册时间: 2011-02-22
最后登录: 2017-02-14
9楼  发表于: 2011-04-03   
厉害厉害,顶一下~~~~我还没有看到那本书
貌似1000多页
描述
快速回复

如果您在写长篇帖子又不马上发表,建议存为草稿
认证码:

验证问题:
printf("%d", 53)
按"Ctrl+Enter"直接提交