动态内存分配
malloc和free
- void* malloc(size_t size);
- 这个函数会在堆内存中分配一块大小为 size 字节的内存,并返回一个指向这块内存的指针。
- 如果分配成功,malloc会返回一个指向分配的内存的指针;如果分配失败,它会返回NULL。
- 当不再使用动态申请的空间用free来释放
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main() {
int *ptr;
int n = 5;
// 分配内存
ptr = (int*)malloc(n * sizeof(int));
if (ptr == NULL) {
printf("%S\n",strerror(errno));//打印错误原因
return 1;
}
// 向分配的内存中写入数据
for (int i = 0; i < n; i++) {
ptr[i] = i + 1;
}
// 打印数据
for (int i = 0; i < n; i++) {
printf("%d ", ptr[i]);
}
// 释放内存
free(ptr);
return 0;
}
calloc
- void *calloc(size_t num, size_t size);
- 用于动态分配内存空间,并将分配的内存空间初始化为零。
- 使用同malloc。
realloc
- void *realloc(void *ptr, size_t size);
- 用于更改先前由malloc、calloc或realloc分配的内存块的大小
- ptr是指向先前分配的内存块的指针。
- size是要重新分配的内存块的新大小(以字节为单位)。
- 如果ptr是NULL,则realloc的行为类似于malloc,分配一个新的内存块,并返回指向这个新内存块的指针。
- 如果size为0,realloc的行为类似于free,释放ptr指向的内存块,并返回NULL。
- 如果ptr不是NULL且size不为0,realloc会尝试重新分配ptr指向的内存块为新的大小。如果重新分配成功,返回指向重新分配后的内存块的指针;如果失败,返回NULL,并且原来的内存块保持不变。
- realloc可能会移动内存块的位置。因此,应该始终将realloc的返回值分配给一个新指针,并且在重新分配后不再使用原来的指针。
- 在重新分配失败时,原来的内存块仍然有效,因此应该检查realloc的返回值来确保内存重分配成功。
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr;
// 分配初始内存块
ptr = (int *)malloc(5 * sizeof(int));
if (ptr == NULL) {
printf("Memory allocation failed.\n");
return 1;
}
// 重新分配内存块的大小为10个整数
ptr = (int *)realloc(ptr, 10 * sizeof(int));
if (ptr == NULL) {
printf("Memory reallocation failed.\n");
return 1;
}
// 使用重新分配后的内存块
for (int i = 0; i < 10; i++) {
ptr[i] = i;
}
// 释放内存块
free(ptr);
return 0;
}
柔性数组(c99)
- 柔性数组只能作为结构体的最后一个成员。
- 柔性数组的大小必须在运行时确定,因为它的大小是结构体大小加上柔性数组的元素个数的总和。
- 内存分配时需要考虑柔性数组的大小。
#include <stdio.h>
#include <stdlib.h>
struct flex_array {
int length;
int data[]; // 柔性数组
};
int main() {
int i;
int array_size = 5;//数组大小
// 分配内存给结构体和柔性数组
struct flex_array *my_array = malloc(sizeof(struct flex_array) + array_size * sizeof(int));
my_array->length = array_size;
// 初始化数组
for (i = 0; i < array_size; i++) {
my_array->data[i] = i * 10;
}
// 访问和打印数组元素
for (i = 0; i < my_array->length; i++) {
printf("Element %d: %d\n", i, my_array->data[i]);
}
// 释放内存
free(my_array);
return 0;
}
文件操作
打开文件:
- 使用fopen()函数来打开一个文件,语法如下:
- FILE *fopen(const char *filename, const char *mode);
- 其中,filename是要打开的文件名,mode指定打开文件的模式,比如"r"表示只读,"w"表示写入(如果文件不存在则创建新文件),"a"表示追加等。
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
printf("Error opening file.\n");
return 1;
}
// 文件操作...
fclose(file);
return 0;
}
关闭文件:
- 使用fclose()函数关闭打开的文件,语法如下:
- int fclose(FILE *stream);
读写文件:
- 可以使用fscanf()和fprintf()函数进行文件的读写操作,也可以使用fread()和fwrite()函数进行二进制数据的读写。
//读文件
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
printf("Error opening file.\n");
return 1;
}
int num;
fscanf(file, "%d", &num);
printf("Read number: %d\n", num);
fclose(file);
return 0;
}
//写文件
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "w");
if (file == NULL) {
printf("Error opening file.\n");
return 1;
}
fprintf(file, "Hello, World!\n");
fclose(file);
return 0;
}
定位文件指针:
- 使用fseek()函数可以移动文件指针到指定位置,语法如下:
- int fseek(FILE *stream, long offset, int whence);
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "r");
if (file == NULL) {
printf("Error opening file.\n");
return 1;
}
fseek(file, 0, SEEK_END); // 将文件指针移动到文件末尾
// 获取文件大小
long size = ftell(file);
printf("File size: %ld bytes\n", size);
fclose(file);
return 0;
}
检查文件结尾:
- 使用feof()函数可以检查文件是否已经到达结尾。
删除文件:
- 使用remove()函数可以删除指定的文件,语法如下:
int remove(const char *filename);
#include <stdio.h>
int main() {
if (remove("example.txt") == 0) {
printf("File deleted successfully.\n");
} else {
printf("Error deleting file.\n");
}
return 0;
}
c语言预处理
程序的执行过程
-
编辑(Edit): 程序员编写源代码文件,通常使用文本编辑器来编写代码。这个阶段主要是编写代码并保存在文件中。
-
预处理(Preprocess): 在编译之前,源代码会经过预处理器处理。预处理器会执行一些指令,比如#include、#define等,将宏展开,处理条件编译等操作。
-
编译(Compile): 预处理完成后,源代码会被编译器翻译成机器能够执行的目标代码(通常是机器码或者中间代码)。编译器会检查代码的语法和语义,生成可执行文件。
-
链接(Link): 如果程序中包含了其他库函数或者模块,编译器会将这些模块连接到程序中,生成最终的可执行文件。链接器会解析符号引用,将不同模块之间的引用关系解决。
-
加载(Load): 可执行文件被加载到内存中,操作系统会为程序分配内存空间,并将程序加载到内存中准备执行。
-
执行(Execute): 程序开始在计算机上执行,按照代码的逻辑顺序执行各个语句,直至程序运行结束。
在Windows的C程序中实现进程间通信
-
管道(Pipes):管道是一种常见的进程间通信机制,可以在父子进程或者兄弟进程之间进行通信。在Windows中,可以使用匿名管道或命名管道。
-
共享内存(Shared Memory):通过共享内存,不同进程可以访问同一块内存区域,实现数据共享。
-
信号量(Semaphores):信号量用于控制多个进程对共享资源的访问,可以通过信号量来同步进程的操作。
-
消息队列(Message Queues):消息队列可以用来在进程之间传递消息,实现进程间通信。
-
套接字(Sockets):套接字通常用于网络编程,但也可以在同一台计算机的不同进程之间进行通信。
//管道方法
//父进程
#include <windows.h>
#include <stdio.h>
#define BUFFER_SIZE 25
int main()
{
HANDLE ReadHandle, WriteHandle;
STARTUPINFO si;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES sa;
char message[BUFFER_SIZE] = "Hello from parent process!";
DWORD written;
// 设置安全属性
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
// 创建管道
if (!CreatePipe(&ReadHandle, &WriteHandle, &sa, 0))
{
fprintf(stderr, "Create Pipe Failed");
return 1;
}
// 设置启动信息
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
si.hStdOutput = WriteHandle;
si.dwFlags = STARTF_USESTDHANDLES;
// 创建子进程
CreateProcess(NULL, "child_process.exe", NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
// 父进程写入消息
WriteFile(WriteHandle, message, BUFFER_SIZE, &written, NULL);
printf("Parent wrote: %s\n", message);
// 等待子进程结束
WaitForSingleObject(pi.hProcess, INFINITE);
// 关闭句柄
CloseHandle(WriteHandle);
CloseHandle(ReadHandle);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
}
//子进程
//子进程的可执行文件命名为child_process.exe
#include <windows.h>
#include <stdio.h>
#define BUFFER_SIZE 25
int main()
{
HANDLE ReadHandle;
STARTUPINFO si;
PROCESS_INFORMATION pi;
char message[BUFFER_SIZE];
DWORD read;
// 获取父进程传递的管道句柄
ReadHandle = GetStdHandle(STD_INPUT_HANDLE);
// 从管道中读取消息
ReadFile(ReadHandle, message, BUFFER_SIZE, &read, NULL);
printf("Child received: %s\n", message);
return 0;
}