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

2021-07-30 19:02:32 +08:00
 JQiue

这是我的环境:

  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 的网站,发现我的代码似乎没什么太大问题,于是才有了这样的疑问

2570 次点击
所在节点    C
21 条回复
typetraits
2021-07-30 19:37:28 +08:00
你的代码里面的
```C
typedef struct
{
ElementType *array;
int front, rear;
int capacity;
int size;
} *Queue;
```

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

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

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

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

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

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

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/792765

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX