V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
JQiue
V2EX  ›  C

今天用学习用 C 实现队列结构时,编译执行阶段出现非常邪乎的问题

  •  
  •   JQiue · 2021-07-30 19:02:32 +08:00 · 2785 次点击
    这是一个创建于 1246 天前的主题,其中的信息可能已经有所发展或是发生改变。

    这是我的环境:

    1. Windows 10
    2. VS Code
    3. MinGW

    下面是我的实现代码:

    #include "stdio.h"
    #include "stdlib.h"
    
    #define OK 1
    #define ERROR 0
    #define CAPACITY 10
    
    typedef int Status;
    typedef int ElementType;
    
    Status initQueue();
    Status isEmpty();
    Status isFull();
    Status enQueue();
    Status deQueue();
    
    typedef struct
    {
      ElementType *array;
      int front, rear;
      int capacity;
      int size;
    } *Queue;
    
    int main(void)
    {
      Queue q = NULL;
      initQueue(&q);
      enQueue(q, 1);
      enQueue(q, 2);
      enQueue(q, 3);
      int foo;
      deQueue(q, &foo);
      deQueue(q, &foo);
      deQueue(q, &foo);
      printf("%d", foo);
      return 0;
    }
    
    Status initQueue(Queue *q)
    {
      (*q) = (Queue)malloc(sizeof(Queue));
      (*q)->capacity = CAPACITY;
      printf("capacity = %d\n", (*q)->capacity);
      (*q)->array = (ElementType *)malloc((*q)->capacity * sizeof(ElementType));
      (*q)->front = (*q)->size = 0;
      (*q)->rear = (*q)->capacity - 1;
      printf("queue = %p, capacity = %d, size = %d, front = %d, rear = %d, array = %p\n", (*q), (*q)->capacity, (*q)->size, (*q)->front, (*q)->rear, (*q)->array);
      return OK;
    }
    
    Status isEmpty(Queue q)
    {
      if (q->size == 0)
      {
        printf("Queqe is empty\n");
        return OK;
      }
      return ERROR;
    }
    
    Status isFull(Queue q)
    {
      if (q->size == q->capacity)
      {
        printf("Queqe is full\n");
        return OK;
      }
      return ERROR;
    }
    
    Status enQueue(Queue q, ElementType e)
    {
      if (isFull(q))
      {
        return ERROR;
      }
      q->rear = (q->rear + 1) % q->capacity;
      q->array[q->rear] = e;
      q->size++;
      printf("%d enqueued to queue\n", e);
      return OK;
    }
    
    Status deQueue(Queue q, ElementType *e)
    {
      if (isEmpty(q))
      {
        return ERROR;
      }
      *e = q->array[q->front];
      q->front = (q->front + 1) % q->capacity;
      q->size--;
      printf("%d dequeued from queue\n", *e);
      return OK;
    }
    

    这个有意思的问题就是在终端中执行结果不太一致

    比如我在 VS code 中的集成终端中( CMD )使用 GCC 编译并执行会是这样的结果:

    gcc SequenceQueue.c -o SequenceQueue && SequenceQueue.exe
    
    capacity = 10
    queue = 006B1810, capacity = 10, size = 0, front = 0, rear = 9, array = 006B1850
    1 enqueued to queue
    2 enqueued to queue
    3 enqueued to queue
    1 dequeued from queue
    2 dequeued from queue
    3 dequeued from queue
    3
    

    如果将集成终端换成 PowerShell,却是这样的结果:

    gcc SequenceQueue.c -o SequenceQueue; .\SequenceQueue
    
    capacity = 10
    queue = 00A015F0, capacity = 268435466, size = 0, front = 0, rear = 268435465, array = 00A01600
    1 enqueued to queue
    2 enqueued to queue
    3 enqueued to queue
    4 dequeued from queue
    2 dequeued from queue
    3 dequeued from queue
    3
    

    这 tm 太奇怪了,capacity 咋就变了,rear 是 capacity - 1 才是这样的结果,而且第一个出栈不是 1 而是 4,很明显 CMD 执行的是正确的结果,也不知道 PowerShell 发生了什么。为了排除 VS Code 的原因,直接单独打开 CMD 和 PowerShell 测试

    CMD:

    gcc SequenceQueue.c -o SequenceQueue && SequenceQueue
    
    capacity = 10
    queue = 007915B8, capacity = 10, size = 0, front = 0, rear = 9, array = 007915F0
    1 enqueued to queue
    2 enqueued to queue
    3 enqueued to queue
    1 dequeued from queue
    2 dequeued from queue
    3 dequeued from queue
    3
    

    PowerShell:

    gcc SequenceQueue.c -o SequenceQueue; .\SequenceQueue
    
    capacity = 10
    queue = 001F2AD0, capacity = 134217738, size = 0, front = 0, rear = 134217737, array = 001F2AE0
    1 enqueued to queue
    2 enqueued to queue
    3 enqueued to queue
    4 dequeued from queue
    2 dequeued from queue
    3 dequeued from queue
    3
    

    这看起来和 VS Code 本身没啥关系,似乎 PowerShell 有点问题?把编译和运行命令分开执行试一下

    gcc .\SequenceQueue.c -o .\SequenceQueue
    
    .\SequenceQueue
    
    capacity = 10
    queue = 00E62AD0, capacity = 134217738, size = 0, front = 0, rear = 134217737, array = 00E62AE0
    1 enqueued to queue
    2 enqueued to queue
    3 enqueued to queue
    4 dequeued from queue
    2 dequeued from queue
    3 dequeued from queue
    3
    

    好吧,看起来还是不行,capacity 的值乱七八糟,第一个出栈的还是 4 而不是 1 。但是,如果不指定生成后的文件名,PowerShell 它似乎又正确了

    (默认生成的 exe 程序名为 a)

    gcc .\SequenceQueue.c
    
    .\a.exe
    
    capacity = 10
    queue = 00B22E40, capacity = 134217738, size = 0, front = 0, rear = 134217737, array = 02144020
    1 enqueued to queue
    2 enqueued to queue
    3 enqueued to queue
    1 dequeued from queue
    2 dequeued from queue
    3 dequeued from queue
    3
    

    但是 capacity 又是乱七八糟的值,那么可能是 GCC 编译的时候出现了问题?于是在 CMD 中使用 GCC 编译并执行程序,然后在 PowerShell 中执行程序

    这是 CMD 中的执行结果:

    gcc SequenceQueue.c -o SequenceQueue
    SequenceQueue.exe
    
    capacity = 10
    queue = 00BB15B8, capacity = 10, size = 0, front = 0, rear = 9, array = 00BB15F8
    1 enqueued to queue
    2 enqueued to queue
    3 enqueued to queue
    1 dequeued from queue
    2 dequeued from queue
    3 dequeued from queue
    3
    

    这是 PowerShell 中的执行结果:

    .\SequenceQueue.exe
    
    capacity = 10
    queue = 001E2AD0, capacity = 134217738, size = 0, front = 0, rear = 134217737, array = 001E2AE0
    1 enqueued to queue
    2 enqueued to queue
    3 enqueued to queue
    4 dequeued from queue
    2 dequeued from queue
    3 dequeued from queue
    3
    

    不是吧,PowerShell 咋回事?那么在 PowerShell 中编译并执行,然后在 CMD 中执行

    这是 PowerShell 的:

    gcc .\SequenceQueue.c
    .\SequenceQueue.exe
    
    capacity = 10
    queue = 00AB2AD0, capacity = 134217738, size = 0, front = 0, rear = 134217737, array = 00AB2AE0
    1 enqueued to queue
    2 enqueued to queue
    3 enqueued to queue
    4 dequeued from queue
    2 dequeued from queue
    3 dequeued from queue
    3
    

    这是 CMD 的:

    gcc SequenceQueue.c -o SequenceQueue
    SequenceQueue.exe
    
    capacity = 10
    queue = 00B515B8, capacity = 10, size = 0, front = 0, rear = 9, array = 00B515F8
    1 enqueued to queue
    2 enqueued to queue
    3 enqueued to queue
    1 dequeued from queue
    2 dequeued from queue
    3 dequeued from queue
    3
    

    妈妈,我想放弃学习了,这都啥跟啥啊,真想一脚踩烂 PowerShell 。好吧,也许不是 GCC 的锅,目前为止我也不知道代码实现是否有哪个地方的问题导致这样的结果,所以希望各位大佬能否复刻一下这种情况,产生这个问题的原因是,我使用 VScode 某个插件一键调用 GCC 编译且执行,总是得到 PowerShell 的结果,然后调试了半天发现不知道问题在哪,于是将代码换到了一些在线运行 C 的网站,发现我的代码似乎没什么太大问题,于是才有了这样的疑问

    21 条回复    2021-08-02 12:21:03 +08:00
    typetraits
        1
    typetraits  
       2021-07-30 19:37:28 +08:00   ❤️ 1
    你的代码里面的
    ```C
    typedef struct
    {
    ElementType *array;
    int front, rear;
    int capacity;
    int size;
    } *Queue;
    ```

    实际上`Queue`是指向一个匿名结构体的指针,Queue 的大小就是一个指针的大小,32 位上 4 字节,64 位上 8 字节

    所以导致你的 initQueue 中 malloc()实际只分配了 8 个字节,赋值操作已经内存溢出了
    typetraits
        2
    typetraits  
       2021-07-30 19:39:09 +08:00
    说错了,看你的 printf 输出的地址,你的系统是 32 位的,那么 Queue 就是 4 字节
    nightwitch
        3
    nightwitch  
       2021-07-30 19:42:27 +08:00
    Status initQueue();
    Status initQueue(Queue *q)
    {
    }

    函数声明和函数实现对不上,放新一点的编译器直接报编译错误。
    nightwitch
        4
    nightwitch  
       2021-07-30 19:43:13 +08:00
    malloc(sizeof(Queue)) 分配的内存分配少了。
    wevsty
        5
    wevsty  
       2021-07-30 20:05:34 +08:00   ❤️ 1
    代码的槽点很多,看的血压升高。

    1 、声明和定义需要一致,不要去糊弄编译器。
    Status initQueue();
    Status initQueue(Queue *q);
    完全是 2 个不同的东西。

    2 、指针和结构体在命名的时候需要区分。
    Queue 这个类型名尽管看上去可能像结构体,但是实际定义成了指针。
    命名不明确这就给你的代码留下了坑。

    3 、sizeof 求的是类型所占用的空间。
    sizeof(Queue) = 指针所占用的大小
    所以 initQueue 中你并没有为结构体真正的分配空间,那么后续的一切内存操作都是未定义的。
    JQiue
        6
    JQiue  
    OP
       2021-07-30 20:24:05 +08:00
    原来如此,多谢各位大佬
    JQiue
        7
    JQiue  
    OP
       2021-07-30 20:35:40 +08:00
    我回去按照各位指出的错误修改了一下代码,确实运行正常了,多谢各位解惑
    JQiue
        8
    JQiue  
    OP
       2021-07-30 20:47:38 +08:00
    @nightwitch 额外请教一下,这个声明是必须要写上参数类型么,我没有注意到这个问题
    YsHaNg
        9
    YsHaNg  
       2021-07-30 21:06:47 +08:00
    @JQiue 声明不写参数类型像你那样那是不接受参数的函数 后面定义又给了参数那就是两个东西 C++是合法的 C 是不能重用函数名的
    JQiue
        10
    JQiue  
    OP
       2021-07-30 21:07:45 +08:00
    @YsHaNg 明白了,谢谢
    kilasuelika
        11
    kilasuelika  
       2021-07-30 21:18:12 +08:00 via Android
    用 C++就不会有这么多毛病
    raysonx
        12
    raysonx  
       2021-07-30 21:36:09 +08:00
    @YsHaNg 传统上 C 语言里面不写参数类型是指可以接受任意多个参数(...),参数与成 void 比如 int foo(void); 才是不接受参数。。。
    YsHaNg
        13
    YsHaNg  
       2021-07-30 22:37:34 +08:00
    @raysonx Variadic function?要用<stdio.h>的啊 plain f([nothing_here])和它不等效吧
    raysonx
        14
    raysonx  
       2021-07-30 22:55:03 +08:00 via iPad
    @YsHaNg 标准库只是自带了一些方便访问栈的宏而已,这是语言特性。
    YsHaNg
        15
    YsHaNg  
       2021-07-30 23:41:39 +08:00
    @raysonx 懂了 不过从来都是规规矩矩写参数列表
    ezrameow
        16
    ezrameow  
       2021-07-31 01:57:51 +08:00
    @JQiue 不同终端运行不正常是因为读取未初始化的内存部分是 ub,返回什么都很正常,而且一般受内存分布和初始化环境影响。。。
    uyun2421
        17
    uyun2421  
       2021-07-31 12:41:14 +08:00 via iPhone
    s🏂🛌🛌🛌😅🈷️😶😅🤔
    uyun2421
        18
    uyun2421  
       2021-07-31 12:41:30 +08:00 via iPhone
    icyalala
        19
    icyalala  
       2021-07-31 13:22:41 +08:00
    编译加上 -Wall -Wextra,有 warning 都看一下,不然自己错哪里怎么错的都弄不清。。
    jedihy
        20
    jedihy  
       2021-07-31 16:01:41 +08:00
    跟 powershell 没任何关系。出现这种问题说明你的程序在读未初始化内存的或者非法的访问。
    araraloren
        21
    araraloren  
       2021-08-02 12:21:03 +08:00
    改用 rust 就不会有这么多毛病了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2683 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 11:46 · PVG 19:46 · LAX 03:46 · JFK 06:46
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.