C代码:将字符串转换为大写时发出警告

jobtbby3  于 2023-04-19  发布在  其他
关注(0)|答案(5)|浏览(192)

我正在使用SIM32CudeIDE用C语言对STM32器件进行编程。
我想将字符串中的小写字符转换为大写字符,并将字符串保留在当前位置。我从网上“偷”了下面的代码,但是我得到了一个警告......对'*String'的操作可能未定义。函数工作正常,我如何修改它以消除警告?
我的准则是

void StrToUpperCase(char *String)
{

    while (*String)
    {
        *String = (*String >= 'a' && *String <= 'z') ? *String = *String - 0x20 : *String;
        String++;
    }

}
ewm0tg9j

ewm0tg9j1#

*String = (*String >= 'a' && *String <= 'z') ? *String = *String - 0x20 : *String;的行为不是由C标准定义的,因为它包含两个对*String的赋值,其中*String的更新没有排序,违反了C 2018 6.5 2:
如果标量对象上的副作用相对于同一标量对象上的不同副作用或使用同一标量对象的值的值计算未排序,则行为未定义...
要解决此问题,应删除不必要的内部指定:

*String = (*String >= 'a' && *String <= 'z') ? *String - 0x20 : *String;

代码中还有其他问题,特别是>= 'a'<= 'z'0x20依赖于特定的字符集编码。至少,假设有一些原因不使用标准的toupper函数,并且我们可以假设大写字母和小写字母都是连续的,并且顺序相同,代码最好写为:

if (*String >= 'a' && *String <= 'z')
    *String += 'A' - 'a';
cdmah0mi

cdmah0mi2#

最左边的*String =的副作用相对于同一个变量的另一个副作用*String = *String - 0x20来说是无序的,所以代码调用了未定义的行为。
?:运算符首先计算第一个操作数(左边),然后在第一个操作数和第二个或第三个操作数之间有一个序列点。
然而,表达式*String = op1 ? op2 :op3;在运算符优先级上等同于*String = (op1 ? op2 : op3);,如果op2op3碰巧也修改了*String,那么这是未定义的行为,因为在op2/op3*String =之间没有序列点。
解决方案:
与流行的看法相反,大多数操作符在一行中不会赢得价格。相反,干净可读的代码赢得了价格:

for(size_t i=0; String[i] != '\0'; i++)
{
  if(String[i] >= 'a' && String[i] <= 'z')
  {
    String[i] -= 0x20;
  }
}

这仍然是有问题的,因为字符表中的字符的算术对于除了'0''9'之外的任何其他字符都没有很好的定义。再次重写代码,我们也可以解决这个问题:

#include <ctype.h>

for(size_t i=0; String[i] != '\0'; i++)
{
  String[i] = toupper(String[i]);
}

toupper被定义为只更改它识别为小写的字符(具有讽刺意味的是,它很可能通过屏蔽0x 20而在内部做到这一点,但没有保证)。

ndasle7k

ndasle7k3#

不要像StrToUpperCase("Hello");那样使用 string literal 调用StrToUpperCase(),因为试图更改字符串literal是 undefined behavior(UB). @CoffeeTableEspresso。
代码中的两个赋值很奇怪:

//      v                                              v                                    
*String = (*String >= 'a' && *String <= 'z') ? *String = *String - 0x20 : *String;

1赋值会更有意义,并且可能会压缩警告:

*String = (*String >= 'a' && *String <= 'z') ? *String - 0x20 : *String;

更好的代码(减少幻数):

*String = (*String >= 'a' && *String <= 'z') ? *String - 'a' + 'A' : *String;

更好的代码:

#include <ctype.h>

char *StrToUpperCase(char *str) {
  //  toupper() designed for unsigned char values (and EOF)
  unsigned char *ustr = (unsigned char *) str;
  while (*ustr) {
    *ustr = toupper(*ustr);
    ustr++;
  }
  return str;
}
new9mtju

new9mtju4#

这就是为什么节省键盘和编写“黑客”代码从来都不是一个好决定的例子。写更多的行,不要使用幻数,并返回值在另一个函数调用中使用。

char *StrToUpperCase(char *String)
{
    char *saved = String;
    if(String)
    {
        while (*String)
        {
            if(*String >= 'a' && *String <= 'z')
            {
                *String += 'A' - 'a';
            }
            String++;
        }
    }
    return saved;
}

它更易于阅读、调试和维护
https://godbolt.org/z/edrodYeYx
但更好的方法是使用标准库函数toupper

eyh26e7m

eyh26e7m5#

Exclusive Or工作也很好

#include <stdio.h>
#include <stdlib.h>


char *Upper(char *str) {
  unsigned char *p = (unsigned char *) str;
  while (*p) {
    if(*p>='a'&&*p<='z'){
        *p=*p^32;
    }
    p++;
  }
  return str;
}
char *Lower(char *str) {
  unsigned char *p = (unsigned char *) str;
  while (*p) {
    if(*p>='A'&&*p<='Z'){
        *p=*p^32;
    }
    p++;
  }
  return str;
}
char *SwitchLowerUpper(char *str) {
  unsigned char *p = (unsigned char *) str;
  while (*p) {
    if((*p>='A'&&*p<='Z') || (*p>='a'&&*p<='z')){
        *p=*p^32;
    }
    p++;
  }
  return str;
}
int main() {
    char test[]="aBcDef";
    printf("%s\n",Upper(test));
    printf("%s\n",Lower(test));
    char test2[]="aBcDef";
    printf("%s\n",SwitchLowerUpper(test2));
    return 0;
}

相关问题