使用Flex&Bison时可能的问题和解决方法

问题1:cannot find ll

意思是找不到 libl.a库文件。

问题2:cannot find fl

意思是找不到 libfl.a库文件。

问题3:cannot find ly

意思是找不到 liby.a库文件。

解决方法

下面是在Ubuntu系统上的解决方法介绍:

  1. 安装apt-file:apt install apt-file
  2. 使用apt-file命令查找库在哪个工具包中:
apt-file search libl.a
apt-file search libfl.a
apt-file search liby.a

查找结果:

3. 安装对应的工具包就可以了

apt install libfl-dev libbison-dev

apt-cache 是使用包名或包描述 检索仓库。

apt-file 是 使用包中文件名检索存储库(工具包名)。

Linux centos 对应的是 yum whatprovides ,具体使用方法请自行查阅。

语法分析器Bison安装

Bison源码地址:https://github.com/akimd/bison

Linux环境安装

方式一

到Bison官网上给定的下载地址:https://ftp.gnu.org/gnu/bison/

下载完成后,解压后进入文件夹目录按照INSTALL文件内容中的安装步骤即可。

  • 执行配置:./configure ,执行后可能会检查出来缺少依赖,按照提示的内容,安装缺少的依赖就可以了。
  • 执行编译和检查:make && make check
  • 执行安装:sudo make install ,默认是安装在 /usr/local/bin目录下。
  • 安装成功后,输入bison --version ,输出版本号就表示成功了。

方式二

直接命令安装:sudo apt install bison

Windows环境安装

在上一篇文章中,Flex安装中已经说明安装了MinGW。下面直接说安装Bison。

选择最新的版本下载,并选择安装到和Flex一个目录中。之前flex的bin目录已经添加到了系统环境path中了。这里直接打开cmd控制台,输入:bison --version 输出正确的版本即成功安装。

词法分析器Flex安装

Flex源码地址:https://github.com/westes/flex/

Linux环境环境安装

方式一

  • 环境说明

Linux Debian x86_64

注意:系统需要安装 cmake 和 make.

  • 安装依赖:sudo apt install help2man m4
  • 下载flex压缩包:wget https://github.com/westes/flex/releases/download/v2.6.4/flex-2.6.4.tar.gz
  • 解压文件:sudo tar -xvf flex-2.6.4.tar.gz
  • 进入文件夹:cd flex-2.6.4
  • 查看INSTALL.md文件中的安装步骤
  • 执行配置命令:./configure 会把列出提示缺少的依赖包,需要把这些缺少的依赖逐个安装一下就行。help2man,m4等
  • 执行编译和检查:make && make check
  • 执行安装:sudo make install 默认是安装在/usr/local/bin 路径下面的。
  • 检查是否安装成:flex --version 成功输出版本号,则是成功。

方式二

直接安装:sudo apt install flex

Windows环境安装

  • 环境说明:系统win10,需要安装MinGW(Minimalist GUN on Windows),是GCC的windows版本,MinGW有2个发行版本:MinGW-64 和 MinGW。区别在于MinGW只能编译生成32位可执行程序,而MinGW-64则可以 编译生成64位和32位的可执行程序。下载地址:https://sourceforge.net/projects/mingw-w64/files/

可以选择以下2种方式安装,如下图:

我选择的是【x86_64-win32-sjlj】,解压后直接将 bin目录配置到系统环境变量path中,然后将mingw32-make.exe重命名为make.exe。

目前这里只提供了32位的工具,如果需要64位的文件需要自己手动编译源码生成。

安装完成后,需要将安装目录下的bin文件夹目录添加到系统环境变量path中。

完成系统环境配置后打开cmd控制台,输入:flex --version 可以打印出版本号。

C语言安全地释放指针

安全释放指针

最近在读有关C语言指针方面的书籍,看到有关malloc系列函数申请内存释放使用宏定义的方式。自己也动手试了一下,感觉还不错记录一下分享给小伙伴们。

实现和应用

#include <stdlib.h>
#include <stdio.h>

void safeFreePtr(void **p_ptr)
{
    if (p_ptr != NULL && *p_ptr != NULL)
    {
        free(*p_ptr);
        *p_ptr = NULL;
    }
}

/** 宏定义 */
#define safeFree(ptr)  safeFreePtr((void **)&(ptr))

int main(int argc, char const *argv[])
{
    int *a = (int *)malloc(sizeof(int));
    *a = 10;
    printf("Before: %p\n", a);
    safeFree(a);
    printf("After: %p\n", a);
    safeFree(a);
    return 0;
}

运行结果:

Before: 0x5634b2e342a0
After: (nil)

可以看出第二次safeFree时打印是:nil,程序没有出错。

对于申请的指针,使用free函数释放时只能free一次,第二次free会报错。

指针free释放后,一定要记得把指针置为NULL,防止悬挂指针。

好处

如果程序中是直接调用safeFreePtr函数是需要显示转换为 void** 指针,若不显示转换则会报警告。在使用宏调用safeFreePtr函数可以避免强制转换

查看系统默认内存对齐字节数

本文使用C语言查看系统默认内存对齐字节数。

#pragma pack(show)

实现当前系统默认对齐字节数,是以警告的方式显示

使用vscode编辑器,写入以下代码:

// 默认是8
#pragma pack(show)

int main(int argc, char const *argv[])
{
   return 0;
}

运行上面的代码后,会在vscode的控制台 【PROBLEMS】tab中显示下面的警告提示信息。

value of #pragma pack(show) == 8 [Semantic Issue]

表示当前内存对齐字节数8.

C基于链表实现栈

本篇内容中的链表是之前的文章中实现的,请参阅文章 C-单链表的实现

栈的特性

先进后出(First-In-Last-Out, FILO).

像队列实现一样,有2个主要的操作:

一)push 操作将元素入栈。

二)pop 操作将元素出栈。

栈的实现

基于单链表实现,push 入栈操作就是往链表的头部添加数据元素,pop 出栈操作就是从链表的头部删除数据元素

以下是栈的定义以及主要操作:

// 定义栈
typedef LinkedList Stack;
// 初始化栈
void initStack(Stack *stack)
{
    init_linkedlist(stack);
}
// 入栈,在链表头部添加元素
void push(Stack *stack, void *data)
{
    addHead(stack, data);
}
// 出栈,从链表头部删除元素
void* pop(Stack *stack)
{
    pNode node = stack->head;
    void* data;
    if (stack->head == NULL) {
        data = NULL;
    } else if (stack->head == stack->tail) {
        stack->head = stack->tail = NULL;
        data = node->data;
        free(node);
    } else {
        stack->head = node->next;
        data = node->data;
        free(node);
    }

    return data;
}

示例说明

    Stack stack;
    // 初始化
    initStack(&stack);
    int a = 20;
    int b = 30;
    int c = 40;
    int *ptr_1 = &a;
    int *ptr_2 = &b;
    int *ptr_3 = &c;

    push(&stack, ptr_1);
    push(&stack, ptr_2);
    push(&stack, ptr_3);

    int *ptr = NULL;
    for (int i = 0; i < 3; i++)
    {
        ptr = (int *)pop(&stack);
        printf("Popped %d\n", *ptr);
    }

运行结果:

Popped 40
Popped 30
Popped 20


以上内容如有错误,欢迎留言指出交流或者关注公众号交流。

C基于链表实现队列

本章内容是基于上一篇文章单链表的实现 C-单链表的实现

队列是先进先出(First in First out)的线性数据结构. 通常有2个主要的操作:enqueue 和 dequeue。

enqueue操作是将元素添加到列队中

dequeue操作是将元素从队列中移除

链表经常用于队列的实现。enqueue 操作是从链表的头部(head)添加元素,dequeue 操作是从链表的尾部(tail)移除元素。

队列定义

定义队列和相关操作

// 定义队列
typedef LinkedList Queue;
// 初始化队列
void initQueue(Queue *queue)
{
    init_linkedlist(queue);
}

// 添加元素
void enqueue(Queue *queue, void *data) {
    addHead(queue, data);
}

// 移除元素
void* dequeue(Queue *queue) 
{
    pNode node = queue->head;
    void* data;
    if (queue->head == NULL){
        data = NULL;
    } else if (queue->head == queue->tail) {
        queue->head = queue->tail = NULL;
        data = node->data;
        free(node);
    } else {
        // 找到倒数第二个节点,由于之前实现的链表是单链表,只能这么找
        while (node->next != queue->tail)
        {
            node = node->next;
        }
        queue->tail= node;
        node = node->next;
        queue->tail->next = NULL;

        data = node->data;
        free(node);
    }

    return data;   
}

示例说明

    Queue queue;
    // 初始化
    initQueue(&queue);
    int a = 20;
    int b = 30;
    int c = 40;
    int *ptr_1 = &a;
    int *ptr_2 = &b;
    int *ptr_3 = &c;

    enqueue(&queue, ptr_1);
    enqueue(&queue, ptr_2);
    enqueue(&queue, ptr_3);

    void *data = dequeue(&queue);
    printf("Dequeued %d\n", *((int*) data));

    data = dequeue(&queue);
    printf("Dequeued %d\n", *((int*) data));

    data = dequeue(&queue);
    printf("Dequeued %d\n", *((int*) data));

结构打印:

Dequeued 20
Dequeued 30
Dequeued 40


以上内容,如有错误,欢迎留言交流或添加公众号交流。

C-单链表的实现

本文将介绍使用C中关键字struct 定义结构体实现单链表。

在C语言中,结构体的自引用只能通过指针来实现,因为在系统中指针的大小是固定(32位系统时大小是4字节,64位系统时大小是8字节)。

结构体自引用实现方式

有2种实现方式,

方式一:

typedef struct tagNode
{
    char *pItem;
    struct tagNode *next;
} *pNode; // 注意结构体的结尾一定得有分号

方式二:

// typedef 一定要定义在struct之前
typedef struct tagNode *pNode; 
struct tageNode
{
    char *pItem;
    pNode next;
};

链表定义

定义链表和相关操作

// 定义一个用显示数据的函数指针
typedef void (*DISPLAY)(void*);
// 定义一个用于比较数据大小的函数指针
typedef int (*COMPARE)(void*, void*);

// 节点定义
typedef struct _node
{
    void *data; // 节点数据
    struct _node *next; // 下一个节点
} *pNode;

// 链表定义
typedef struct _linkedlist
{
    pNode *head;
    pNode *tail;
    pNode *current;
} LinkedList;

// 初始化链表
void init_linkedlist(LinkedList *list)
{
    list->head = NULL;
    list->tail = NULL;
    list->current = NULL;
}

// 向链表头添加数据节点
void addHead(LinkedList *list, void *data)
{
    pNode node = (pNode) malloc(sizeof(Node));
    node->data = data;
    if (list->head == NULL) {
        list->tail = node;
        node->next = NULL;
    } else {
        node->next = list->head;
    }
    list->head = node;
}

// 向链表尾部添加数据节点
void addTail(LinkedList *list, void *data)
{
    pNode node = (pNode) malloc(sizeof(Node));
    node->data = data;
    node->next = NULL;
    if (list->head == NULL) {
        list->head = node;
    } else {
        list->tail->next = node;
    }
    list->tail = node;
}

// 获取指定的节点信息
pNode getNode(LinkedList *list, COMPARE compare, void *data) 
{
    pNode node = list->head;
    while (node != NULL) {
        if (compare(node->data, data)) {
            return node;
        }
        node = node->next;
    }
    return NULL;
}
// 删除指定的节点
void deleteNode(LinkedList *list, pNode node)
{
    if (node == list->head)
    {
        if (list->head->next == NULL) {
            list->head = list->tail = NULL;
        } else {
            list->head = list->head->next;
        }
    } else {
        pNode tmp = list->head;
        while (tmp != NULL && tmp->next != node)
        {
            tmp = tmp->next;
        }
        if (tmp != NULL) {
            tmp->next = node->next;
        }
    }
    
    free(node);
}
// 展示数据
void displayList(LinkedList *list, DISPLAY display)
{
    printf("\nLinkedList shown:\n");
    pNode node = list->head;
    while (node != NULL) {
        display(node->data);
        node = node->next;
    }
}

// 比较函数
int compareInt(int *e1, int *e2)
{
    return *e1 == *e2;
}
// 显示数据
void displayIntVal(int *e)
{
    printf("%d\n", *e);
}

使用示例

1.从链表头部开始添加数据

    LinkedList linkedList;
    // 初始化
    init_linkedlist(&linkedList);
    int a = 20;
    int b = 30;
    int c = 40;
    int *ptr_1 = &a;
    int *ptr_2 = &b;
    int *ptr_3 = &c;

    addHead(&linkedList, ptr_1);
    addHead(&linkedList, ptr_2);
    addHead(&linkedList, ptr_3);

    displayList(&linkedList, (DISPLAY)displayIntVal);

打印结果:

LinkedList shown:
40
30
20

2.从链表尾部添加数据

    LinkedList linkedList;
    // 初始化
    init_linkedlist(&linkedList);
    int a = 20;
    int b = 30;
    int c = 40;
    int *ptr_1 = &a;
    int *ptr_2 = &b;
    int *ptr_3 = &c;

    addTail(&linkedList, ptr_1);
    addTail(&linkedList, ptr_2);
    addTail(&linkedList, ptr_3);

    displayList(&linkedList, (DISPLAY)displayIntVal);

打印结果:

LinkedList shown:
20
30
40

3.获取指定的节点

    LinkedList linkedList;
    // 初始化
    init_linkedlist(&linkedList);
    int a = 20;
    int b = 30;
    int c = 40;
    int *ptr_1 = &a;
    int *ptr_2 = &b;
    int *ptr_3 = &c;

    addTail(&linkedList, ptr_1);
    addTail(&linkedList, ptr_2);
    addTail(&linkedList, ptr_3);

    pNode node = getNode(&linkedList, (COMPARE) compareInt, ptr_2);
    printf("node val is %d\n", *((int *)node->data));

打印结果:

node val is 30

4.删除指定的节点

    LinkedList linkedList;
    // 初始化
    init_linkedlist(&linkedList);
    int a = 20;
    int b = 30;
    int c = 40;
    int *ptr_1 = &a;
    int *ptr_2 = &b;
    int *ptr_3 = &c;

    addTail(&linkedList, ptr_1);
    addTail(&linkedList, ptr_2);
    addTail(&linkedList, ptr_3);

    pNode node = getNode(&linkedList, (COMPARE) compareInt, ptr_2);
    printf("node val is %d\n", *((int *)node->data));

    deleteNode(&linkedList, node);
    displayList(&linkedList, (DISPLAY)displayIntVal);

打印结果:

node val is 30

LinkedList shown:
20
40


以上就是全部的内容,如有错误,欢迎留言交流或者关注公众号交流。

对C中const修饰指针的使用和理解

const用于定义常量类型,限定某些变量不允许修改。

const修饰指针时的原则:先忽略掉类型说明符,离谁近就修饰谁

1. 常量指针

常量指针:是指向常量的指针,指针指向地址中的内容是不可修改的,但是指针的指向是可以修改。

定义方式,以下2种都可以:

  1. const dataType *ptrName;
  2. dataType const *ptrName;
int a = 20;
int b = 30;
const int *ptr_i = &a;
// *ptr_i = 30; // 报错:read-only variable is not assignable 只读变量不可赋值
ptr_i = &b; // OK的

指针指向的内容不可修改,即:*ptrName(解引用时)表示的值不修改。

const修饰的是*ptr_i,所以不能修改的是就是 *ptr_i 的值。

2. 常指针(指针常量)

常指针:是指针类型的常量,即指针本身的值不可以修改,始终指向一个地址。因此常指针初始化需要给定一个地址值。但是指向地址的内容可以修改

定义方式,以下方式都是可以的(必须要初始化设置值):

  1. dataType *const ptrName;
  2. const *dataType ptrName;
int a = 20;
int *const ptr1 = &a;
//ptr1 = NULL; // 报错:cannot assign to variable 'ptr1' with const-qualified type 'int *const' 
*ptr1 = 30; // OK的

指针本身的值不可以修改,ptr1的值是&a,不可以修改,指针指向地址的内容可以修改即*ptr1(解引用时)表示的值可以修改。

const修饰的是ptr1,所以ptr1的值不能修改。

3. 常指针&常量指针的复合使用

int a = 30;
const int *const ptr_a = &a; 

指针的地址值和地址值的内容 都不可以修改。

第一个const 修饰的是 *ptr_a,所以 *ptr_a 的值不能修改。

第二个const 修饰的是 ptr_a,所以 ptr_a的值也不能修改。

4. 其他

const 常用于修饰函数的参数,防止函数内部修改外部变量。

对C中typedef的使用和理解

typedef声明为已存在的数据类型取一个别名,可以用于替换复杂的类型名。

typedef是存储类型说明符(typedef、auto、register、static、extern、_Thread_local),由于声明中只允许一个存储类型说明符,因此typedef不能和他的存储类型说明符一起使用

使用代码示例说明

// 为整型定义一个别名int32
typedef int int32;

// 为 int* 指针定义一个别名
typedef int* int_ptr;

// 1.为 char 定义一个类型别名 char_t
// 2.为 char* 定义一个类型别名 char_p(char类型指针)
// 3.为 char (*)(void) 定义一个类型别名 fptr(函数指针)
typedef char char_t, *char_p, (*fptr)(void);

// 定义可变长度数组
typedef int A[];
// 使用时确定具体长度,a的类型是int[2], b的类型是 int[3]
A a = {1, 2}, b = {3,4,5};

// 为 int[5] 定义一个别名为arr
typedef int arr[5];

// 为结构体取别名
// 1.为 _node 定义一个别名 Node
// 2.为 _node* 定义一个别名 P_Node (指针)
typedef struct _node
{
   void* data;
   struct _node *pNext;  // 结构体自引用
} Node, *P_Node;

// 为 tageNode* 定义一个别名 pNode
typedef struct tagNode *pNode;
struct tageNode
{
    char *pItem;
    pNode pNext; // 结构体自引用
};

注意:结构体的自引用只能通过指针来引用。

通过上面的代码和说明可以看出:把typedef和别名去掉后,剩余部分就是别名代表的数据类型

Copyright © 2023 蚕鸣夏季 . All rights reserved. 备案号:苏ICP备19001383号-1