零碎的C语言基础知识第四部分(动态内存分配、文件操作、c语言预处理、在Windows的C程序中实现进程间通信)

动态内存分配

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;
}


  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值