【问题标题】:C program immediatley exits after execution, yet it has a while(1) loopC程序执行后立即退出,但它有一个while(1)循环
【发布时间】:2020-08-22 03:40:11
【问题描述】:

我正在尝试将 c 中的扫雷程序安装在 3KB 限制下,以便它可以放入 qr 代码中,并且它工作正常,直到我添加选项 -nostdlib 并编译它,程序立即退出,尽管 main 中有一个 while(1) 循环,但无法退出。

我在链接器中添加的库是kernel32gccmsvcrt(如果您想重新生成它)。虽然它在技术上确实返回了 0x0,这意味着它是“成功的”,但显然不是;否则它不会那样做。我的代码可以在这里看到:

#include <windows.h>

#define WIDTH 100
#define HEIGHT 100
#define BOMBS 801

int xorshf96(int x, int y, int z)
{
   unsigned long t;
   x ^= x << 16;
   x ^= x >> 5;
   x ^= x << 1;

   t = x;
   x = y;
   y = z;
   z = t ^ x ^ y;

  return z;
}


void ExpandGrid(int fullGrid[WIDTH][HEIGHT], int knownGrid[WIDTH][HEIGHT], int blankPos[2])
{
    int neighbors[8][2] = {{0,1}, {1,0}, {1,1},
                          {0,-1},        {-1,0},
                          {-1,-1},{-1,1},{1,-1}};
    int curTile[2];

    knownGrid[blankPos[0]][blankPos[1]] = 1;
    if(fullGrid[blankPos[0]][blankPos[1]] != 0) return;

    for(int blck = 0; blck < 8; ++blck)
    {
        curTile[0] = abs(blankPos[0]+neighbors[blck][0]);
        curTile[1] = abs(blankPos[1]+neighbors[blck][1]);
        if(curTile[0] > WIDTH-1 || curTile[1] > HEIGHT-1) continue;

        if(fullGrid[curTile[0]][curTile[1]] == 0 && knownGrid[curTile[0]][curTile[1]] == 0)
        {
            knownGrid[curTile[0]][curTile[1]] = 1;
            ExpandGrid(fullGrid, knownGrid, curTile);
        }
        else if(fullGrid[curTile[0]][curTile[1]] > 0) knownGrid[curTile[0]][curTile[1]] = 1;
    }
}


int main(void)
{
    COORD characterBufferSize = { WIDTH, HEIGHT };
    COORD characterPosition = { 0, 0 };
    SMALL_RECT consoleWriteArea = { 0, 0, WIDTH - 1, HEIGHT - 1 };
    CHAR_INFO consoleBuffer[WIDTH][HEIGHT];

    HANDLE wHnd = GetStdHandle(STD_OUTPUT_HANDLE);
    HANDLE rHnd = GetStdHandle(STD_INPUT_HANDLE);

    int num1, num2, num3 = 0;
    SYSTEMTIME systime;

    DWORD numEventsRead = 0;
    DWORD numEvents = 0;
    INPUT_RECORD *eventBuffer;
    int wait = 0;

    SetConsoleTitle("Minesweeper!");

    int startGrid[WIDTH][HEIGHT] = { 0 };
    int knownGrid[WIDTH][HEIGHT] = { 0 };
    int arrowPos[2] = {0, 0};
    int bomb[2] = {0, 0};

    for (int i = 0; i < BOMBS; i++)
    {
        while (startGrid[bomb[0]][bomb[1]] < -1 || bomb[0] <= 0 || bomb[1] <= 0 || bomb[0] >= WIDTH-1 || bomb[1] >= HEIGHT-1)
        {
            GetLocalTime(&systime);
            num1, num2, num3 = (int) systime.wMilliseconds;

            bomb[1] = (xorshf96(num3, num2, num1) % (HEIGHT-1)) + 1;

            GetSystemTime(&systime);
            num1, num2, num3 = (int) systime.wMilliseconds;

            bomb[0] = (xorshf96(num1, num2, num3) % (WIDTH-1)) + 1;
        }

        startGrid[bomb[0]][bomb[1]] = -9;

        startGrid[bomb[0] + 1][bomb[1] + 1]++;
        startGrid[bomb[0] + 1][bomb[1]]++;
        startGrid[bomb[0]][bomb[1] + 1]++;
        startGrid[bomb[0] - 1][bomb[1] + 1]++;
        startGrid[bomb[0]][bomb[1] - 1]++;
        startGrid[bomb[0] + 1][bomb[1] - 1]++;
        startGrid[bomb[0] - 1][bomb[1] - 1]++;
        startGrid[bomb[0] - 1][bomb[1]]++;
    }

    while(1)
    {
        if (arrowPos[0] > WIDTH-1) arrowPos[0] = WIDTH-1;
        if (arrowPos[0] < 0) arrowPos[0] = 0;
        if (arrowPos[1] > HEIGHT-1) arrowPos[1] = HEIGHT-1;
        if (arrowPos[1] < 0) arrowPos[1] = 0;

        for (int x = 0; x < WIDTH; ++x)
        {
            for (int y = 0; y < HEIGHT; ++y)
            {

                if (knownGrid[x][y] == 1)
                {
                    if (startGrid[x][y] > 0)
                    {
                        consoleBuffer[x][y].Char.AsciiChar = '0' + startGrid[x][y];
                        consoleBuffer[x][y].Attributes = FOREGROUND_GREEN | FOREGROUND_INTENSITY;
                    }
                    else
                    {
                        consoleBuffer[x][y].Char.AsciiChar = 'o';
                        consoleBuffer[x][y].Attributes = (startGrid[x][y] < 0 ? FOREGROUND_RED : FOREGROUND_BLUE) | FOREGROUND_INTENSITY;
                    }
                }
                else
                {
                    consoleBuffer[x][y].Char.AsciiChar = 00;
                    consoleBuffer[x][y].Attributes = FOREGROUND_BLUE | FOREGROUND_INTENSITY;
                }

                if(arrowPos[0] == x && arrowPos[1] == y)
                {
                    consoleBuffer[x][y].Attributes = BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_GREEN;
                }
            }
        }

        WriteConsoleOutputA(wHnd, *consoleBuffer, characterBufferSize, characterPosition, &consoleWriteArea);

        numEvents = 0;
        numEventsRead = 0;
        GetNumberOfConsoleInputEvents(rHnd, &numEvents);

        if (numEvents)
        {
            eventBuffer = malloc(sizeof(INPUT_RECORD) * numEvents);
            ReadConsoleInput(rHnd, eventBuffer, numEvents, &numEventsRead);
        }

        if(numEventsRead && wait <= 0)
        {
            wait = 50;
            switch (eventBuffer[0].Event.KeyEvent.wVirtualKeyCode)
            {
                case VK_UP:
                    arrowPos[0]--;
                    break;
                case VK_DOWN:
                    arrowPos[0]++;
                    break;
                case VK_LEFT:
                    arrowPos[1]--;
                    break;
                case VK_RIGHT:
                    arrowPos[1]++;
                    break;
                case VK_RETURN:
                    ExpandGrid(startGrid, knownGrid, arrowPos);
                    break;
            }
        }

        wait--;
    }

}

【问题讨论】:

  • -nostdlib 从内存中抛弃标准库启动函数。虽然添加 libgcc 可能会修复前者,但我看不出您在哪里尝试修复后者。在调用 main 之前通常会发生很多事情。
  • @paxdiablo 我该如何解决这个问题?由于我需要我的程序需要非常小,它要么是-nostdlib 要么是-nodefaultlibs,并且删除默认库似乎会导致更多问题。
  • 我认为可以手动链接-nostdlib之后的cr*对象。
  • @SuraajKS cr* 对象是什么?以及如何链接它们?

标签: c compiler-errors compilation linker


【解决方案1】:

使用-nostdlib两个效果:

  • 它阻止标准库的东西被自动链接;和
  • 它停止链接 C 运行时启动代码 crt0

最后一个很重要,在调用main() 之前通常会发生很多事情,如果您不链接crt0,那么真正的 入口点_start( not main) 不存在。

所以解决方案是提供您自己的_start,但没有真正的 C 程序所需的所有杂乱的 C 环境设置。这就像提供mystart.S 一样简单,如下所示:

.globl _start
_start:
    call main           # call main without any setup.
    movl %eax, %ebx     # copy return value to pass back.
    movl $1, %eax       # function code for exit.
    int $0x80           # invoke function.

将它与一个非常基本的C程序结合起来,如下所示:

int main(void) {
    return 42;
}

然后你可以编译和测试它,按照以下脚本:

pax> gcc -nostdlib -o nolibc nolibc.c mystart.S
pax> ./nolibc ; echo $?
42

这回答了您的具体问题,“为什么它不运行?”。然而,我仍然得到了一个相当大的可执行文件大小,即使 从其他地方收集了一些额外的优化:

gcc -nostdlib -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -o nolibc nolibc.c mystart.S
strip -s -R .comment -R .gnu.version --strip-unneeded nolibc

因此,除此之外,您可能还需要修改链接器脚本,让它只加载您的可执行文件运行所绝对必要的内容。

我不会在这里讨论,因为这是一个相当大的主题,与您的具体问题无关。我建议(当然在搜索之后)问一个关于如何做到这一点的不同问题。


请记住,int $80 是 Linux 的想法,来自记忆。如果你想为 Windows 做类似的事情,你应该检查crt0 代码在那个平台上是如何工作的。它很可能使用ExitProcess(),所以你需要类似的东西(未经测试,所以你需要自己调试):

.globl _start
.extern ExitProcess
_start:
    call main           # call main without any setup.
    push %eax           # push main return code for delivery to OS.
                        # win64 equiv is, I think: mov %eax, %ecx
    call ExitProcess    # And off we go.

然后确定如何与 Windows dll/lib 文件链接。

【讨论】:

  • @Basile:不太可能,因为int $80 是 Linux 的想法。 Windows 等效项将让您调用 ExitProcess 参数为零。我会将其添加到答案中,但这不是确定的,因为我在该领域的专业知识有限。但是,方法应该可行。
  • @paxdiablo 当我使用命令gcc -lkernel32 -lgcc -lmsvcrt -nostdlib -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -o main main.c mystart.S 尝试您的建议时,我遇到了使用-nodefaultlibs 选项时遇到的一些错误。大量未定义的引用:pastebin.com/y4v1S3mB
  • @TryingMyBest:是的,如果您尝试使用 不在您链接的文件中的内容,您将获得未定义的引用。我的方法故意有一个最小的main,所以这没有发生。您需要做的是,对于每个未定义的引用,修改您的代码以便您不使用它,或者在提供它的文件中链接。如果你使用你想要使用的所有东西,我不建议你是否能够低于 3K,我只是展示了如何让你的代码在启动时编译。
  • @paxdiablo 但这些函数所属的库是kernel32.lib,我已经包含了它。所以我想知道为什么它说它们是未定义的引用?
  • @TryingMyBest:如果您确定它们在您要链接的库中,那么这可能是一个排序问题。许多链接器,当给定一个库时,将使用该库来满足当前未满足的符号。由于符号在main 中被引用,那是它们变得不满意的地方,所以您应该将所有-l&lt;libname&gt; 参数移到之后 mainmystart
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-21
  • 1970-01-01
  • 2013-03-22
  • 1970-01-01
相关资源
最近更新 更多