【问题标题】:C program behaving differently on Mac OSX and LinuxC 程序在 Mac OSX 和 Linux 上的行为不同
【发布时间】:2018-05-01 07:27:34
【问题描述】:

我编写了一个程序来创建横向滚动选取框效果,它可以在我的 Mac 终端上完美运行。但是,当我在我的树莓派(Raspbian Linux)上运行它时,效果会变得混乱并开始在新行上打印并且不会滚动整个文本长度。谁能弄清楚问题是什么?我已经尝试了好几天了。

// Compile: gcc marquee.c -o marquee -lncurses
// Usage: ./marquee filename length row col speed
// Reads text from a file and displays 'length' number of chars 
// scrolling sideways at a given 'row, col' position at some indicated 'speed'

#include    <curses.h>
#include    <string.h>
#include    <unistd.h>
#include    <stdio.h>
#include    <stdlib.h> 
#include    <fcntl.h>

#define ROW 10

int main(int ac, char *av[])
{
    if(ac != 6){
        printf("marquee [fileName] [row] [col] [speed (1-99)]\n");
        perror("Insuffecient argument count\n"); 
        exit(1); 
    }

    char message[256];
    int text_length;
    int i;
    int k;  
    int orgPos = atoi(av[4]);
    int pos;
    int row = atoi(av[3]);  
    int dir = 1; 
    int maxPos = atoi(av[2]);

    int speed = atoi(av[5]);

    int filedesc = open(av[1], O_RDONLY); 

    if(filedesc < 0) {
        perror("Could not open file");
        exit(1); 
    } 

    if(speed < 10) 
        speed = 500000; 
    if(speed >= 10 && speed < 20) 
        speed = 250000; 
    if(speed >= 20 && speed < 30) 
        speed = 120000; 
    if(speed >= 30 && speed < 40) 
        speed = 100000; 
    if(speed >= 40 && speed < 60) 
        speed = 80000; 
    if(speed >= 60 && speed < 70) 
        speed = 60000; 
    if(speed >= 70 && speed < 80) 
        speed = 40000; 
    if(speed >= 80 && speed < 90) 
        speed = 20000; 
    if(speed >= 90 && speed < 95) 
        speed = 10000; 
    if(speed >= 95 && speed <= 99) 
        speed = 5000; 

    int bytesRead = 0; 
    while(bytesRead == read(filedesc, message, 256) > 0){

    }

    // Get text length
    text_length = strlen(message);

    initscr(); // initialize curses
    clear();
    curs_set(0);

    while(1) {
        clear(); // clear last drawn iteration

        pos = orgPos;

        char * scroll;
        scroll = malloc(2*maxPos);

        for(i = 0; i<2*maxPos; i++) 
        {
            scroll[i] = message[i%text_length];
        }

        for(i = 0; i < 1000; i++){
                mvaddnstr(row, orgPos, &scroll[i%maxPos], maxPos);
                usleep(speed);
                refresh();
        }
    }

    endwin();

    if(close(filedesc) == -1) 
    {
        perror("Error closing file"); 
        exit(1); 
    }
}

这里肯定有很多改进,但请让您的调试目标弄清楚为什么它不能在 linux 上正确运行。这是一个示例测试用例:

$ ./marquee scroll.txt 25 0 0 50

scroll.txt 包含以下内容:

Hello, this is a test for the scrolling marquee. 

【问题讨论】:

  • 错误的编译命令:应该使用gcc -Wall -Wextra -g marquee.c -o marquee -lncurses然后use the gdb debugger。成为scaredundefined behavior
  • 另外,Stack Overflow 不是 fix-my-bugs 服务,所以你的问题是题外话。
  • 我的意思是更多地提出这个问题,“为什么它在两个操作系统之间的行为不同?”因为一个工作正常,另一个不行;与其说是“修复我的错误”。
  • 文件不起作用(即使在您的笔记本电脑上),您仍然有未定义的行为。
  • 我在 Solaris 11.4 上运行了您的程序(唯一需要的更改是包含 &lt;ncurses.h&gt; 而不是 &lt;curses.h&gt;)。它对我来说运行得很好,没有你提到的终端窗口损坏发生在 linux 上。当谈到终端输出差异时,我的第一步是检查终端类型。在 Solaris 上,我的默认术语类型是 xterm-256color。在我运行 10.13.4 的 Mac 上,它是 xterm。然后我尝试了几种不同的术语类型:TERM=ansi TERM=xterm TERM=xterms TERM=at386 TERM=dtterm,它们都显示了未损坏的滚动输出。

标签: c linux macos unix ncurses


【解决方案1】:

您有一个automatic variable char message[256];。您没有显式初始化它,因此它在开始时包含垃圾。很难(甚至不可能,想想ASLR 或环境是如何传递的,也许通过main 的第三个参数)来预测其中的确切垃圾是什么。

您正在使用read(filedesc, message, 256),但read(2) 不会在message 的末尾添加任何NUL 字节,除非该字节在文件中;所以使用strlen(message)undefined behavior (UB),你应该是scared

效果很好

错误。有时,当您运气不佳时,UB 似乎偶然(而不是“完美地”)工作。这就是它未定义的原因。这可能是 MacOSX 下的情况。但是,即使您的程序(错误地并且运气不好)似乎可以工作,它仍然存在很多错误。

UB 是未定义的,因此如果不深入研究血淋淋的实现细节,就无法解释行为差异。如果你真的关心这些(但你真的不应该),研究你的编译器的源代码,你的内核代码(以及在execve(2)之后会发生什么,特别是在crt0中),生成的汇编代码(使用gcc -S -fverbose-asm -O ) 来了解message 里面有什么样的垃圾数据。您可能需要花费数年时间才能了解所有血淋淋的细节。

你应该替换(注意你的==是非常错误的)

     while(bytesRead == read(filedesc, message, 256) > 0){ //BAD

至少(使用comma operatorassignment expression)清除message 并保持读取字节数:

    while ((memset(message, 0, sizeof(message)),
           (byteread = read(filedesc, message, sizeof(message)-1) > 0) {

由于message 的最后一个字节永远不会是read,它保持其0 值(并且byteread 始终小于256,sizeof(message) ...)。

您的程序中还有其他错误。保持恐惧(请参阅 this 以获取灵感)。

尝试使用调试器。使用cross-compilation,可以将use the gdb debugger 用于remote debugging。将-g -Wall 传递给您的交叉编译器,如果它是一些GCC。也许在你的树莓派上使用valgrind

也许还可以使用一些static program analyzer,例如clang-analyzerFrama-C。需要注意的是,它们可能需要额外的技能和注释(例如,在 ACSL 中用于 Frama-C)才能有用,并且可以提供大量 false alarms(请记住 Halting Problemundecidable,所以不要期望太多来自静态程序分析器和编译器)。

顺便说一句,关于未初始化内存的正确想法是相信(这是一个有用的虚构)它携带某种“疾病”,这种“疾病”会传播到所有使用它的东西。当然,这不是计算机内部发生的事情。但是这样想会帮助你避免 UB。

【讨论】:

  • or even impossible, think of ASLR ASLR 随机化虚拟地址空间,而不是实际的页框。我不相信它会影响对未初始化内存内容的预测
  • ASLR 可能(并且在 Linux 上确实)随机化初始堆栈段地址范围,因此会影响 main 的堆栈帧,例如通过env 第三个参数到main
猜你喜欢
  • 1970-01-01
  • 2012-03-22
  • 1970-01-01
  • 2019-08-20
  • 2023-03-31
  • 1970-01-01
  • 2013-12-24
  • 2014-01-30
  • 2018-06-04
相关资源
最近更新 更多