strlen return c 中的错误答案

strlen return wrong answer in c

我正在编写一个将十进制数转换为罗马数的程序。我使用 4 个数组 thousandshundredstensunits 来存储罗马数字中的数字,然后将每个数字复制到 res 数组,我使用 str 指向跟踪 res 中字符串开始位置的指针。当我用输入 128 进行测试时,它会打印出 CXXVIIIIX,它必须是 CXXVIII。我试过调试得到的结果是 tmp=8strlen(units[tmp-1]) = 6,这意味着 strlen 也算作 IX。对于某些情况,例如 3888,程序会打印出垃圾值。

这是我的代码

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdbool.h>
#include <string.h>
#include <windows.h>

int main(){
    int n=128;

    char thousands[][4]={"M","MM","MMM"};
    char hundreds[][5]={"C","CC","CCC","CD", "D", "DC", "DCC", "DCCC", "CM"};
    char tens[][5]={"X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"};
    char units[][5]={"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"};

    char res[16];
    res[15]='[=11=]';    //add nul to the last character of res array
    char *str=res+14;   //str tracks where the string start
    int tmp;
    
    
    //store roman digits in res array in reverse order, 
    //start from units->tens->hundreds->thousands
    if (n!=0)
    {
        tmp=n%10;
        if (tmp!=0)
        {
            str-=strlen(units[tmp-1]);  //str steps back several address to store new digits
            strncpy(str, units[tmp-1], strlen(units[tmp-1]));   //use strncpy to copy without '[=11=]'
            
        }
        n/=10;
    }
    
    if (n!=0)
    {
        tmp=n%10;
        if (tmp!=0)
        {
            str-=strlen(tens[tmp-1]);
            strncpy(str, tens[tmp-1], strlen(tens[tmp-1]));
            
        }
        n/=10;
    }
    
    if (n!=0)
    {
        tmp=n%10;
        if (tmp!=0)
        {
            str-=strlen(hundreds[tmp-1]);
            strncpy(str, hundreds[tmp-1], strlen(hundreds[tmp-1]));
            
        }
        n/=10;
    }
    
    if (n!=0)
    {
        tmp=n%10;
        if (tmp!=0)
        {
            str-=strlen(thousands[tmp-1]);
            strncpy(str, thousands[tmp-1], strlen(thousands[tmp-1]));
            
        }
        n/=10;
    }
    
    printf("%s", str);
    return 0;
}

那么为什么会发生这种情况以及如何解决它?

如有任何帮助,我们将不胜感激。

修复所有字符串文字的数组大小后,出现差一错误和未初始化的 char 数组元素。

  • char res[16]; 对于 "MMMDCCCLXXXVIII" 和尾随 '[=14=]' 就足够了。
  • 使用 res[15]='[=15=]'; 将字符串终止存储在最后一个元素中。
  • char *str=res+14; 设置指向在此之前未初始化字符的指针。
  • str-=strlen(something) 根据要插入的字符串的长度移动指针。以下strncpy不会覆盖未初始化的字符

结果将始终包含未初始化的尾随字符,该字符可能可见也可能不可见。

由于尾随未初始化字符,最大长度 ("MMMDCCCLXXXVIII") 的结果将在第一个数组元素之前的一个字符开始。您将得到一个结果字符串 "MMMDCCCLXXXVIII*",其中 * 未初始化。第二个 Mres 的第一个数组元素,第一个 M 将是第一个元素之前的元素。

演示差一错误的示例:

基于问题的原始(错误)代码。

请注意,这不是完整代码,而是一个简化的摘录,旨在显示变量值如何随输入值变化 n=3888。 (为了使代码更短,此处缺少原始代码中的 if (tmp!=0) 守卫。)

在显示变量值的注释中,*用于表示未初始化的字符。

int n=3888;

char res[16];
// * indicates an uninitialized character
res[15]='[=10=]';  // "***************[=10=]"

// The following initialization is wrong.
char *str=res+14; // str = &res[14] (the last uninitialized character)
int tmp;

// The following block leaves the last character of res uninitialized.
tmp=n%10; // tmp = 8
str-=strlen(units[tmp-1]); // "VIII" (strlen = 4) -> str = &res[10]
strncpy(str, units[tmp-1], strlen(units[tmp-1])); // "**********VIII*[=10=]"
n/=10; // n = 388

// Here, str points to the first character written by the previous block.
tmp=n%10; // tmp = 8
str-=strlen(tens[tmp-1]); // "LXXX" (strlen = 4) -> str = &res[6]
strncpy(str, tens[tmp-1], strlen(tens[tmp-1])); // ******LXXXVIII*[=10=]"
n/=10; // n = 38

/* ...*/

// final result would be

// str = &res[-1]
// str -> "MMMDCCCLXXXVIII*[=10=]"
// res =   "MMDCCCLXXXVIII*[=10=]"

// The last strncpy tries to write a character 'M' one 
// element before the beginning of the array res
// corresponding to strncpy(res[-1], "MMM", 3)

我建议作为修复

char res[16] = {0}; // initialize the whole array with 0
char *str=res+(sizeof(res)-1);

... and how to fix it?

  • 考虑以 1,000 为单位编写单位,然后是 100、十、个位的顺序。

  • 在每个单元列表中添加一个“零”。

  • 滚入辅助函数。

  • 将类型从字符数组更改为指针数组。

例子

void print_roman(int n) {
  static const char *thousands[] = {"", "M", "MM", "MMM"};
  static const char *hundreds[] = {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"};
  static const char *tens[] = {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"};
  static const char *ones[] = {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"};
  static const char **units[] = {thousands, hundreds, tens, ones};
  int units_n = sizeof units / sizeof units[0];

  assert(n > 0 && n < 4000);
  char res[16];
  char *p = res;
  int multiplier = 1000;
  for (int i = 0; i < units_n; i++) {
    strcpy(p, units[i][n / multiplier]);
    p += strlen(p);
    n %= multiplier;
    multiplier /= 10;
  }
  printf("<%s>\n", res);
}

int main() {
  print_roman(3888);
  print_roman(3999);
  print_roman(1010);
  print_roman(42);
}

输出

<MMMDCCCLXXXVIII>
<MMMCMXCIX>
<MX>
<XLII>