【问题标题】:When is it okay to compare C strings by char * equality?什么时候可以通过 char * 相等来比较 C 字符串?
【发布时间】:2018-06-26 10:44:25
【问题描述】:

我知道通过比较两个任意 c 字符串 (const char *) (a == b) 是没有意义的。

但我认为当两者都由相同的字符串文字定义时,这是合法的。

例如,这里:

#include <stddef.h>

const char * const meals[] = {
    "none",
    "breakfast",
    "lunch",
    "dinner"
};

#define NO_MEALS  meals[0]
#define BREAKFAST meals[1]
#define LUNCH     meals[2]
#define DINNER    meals[3]

// i hours after midnight, hour_to_meals_map[floor(i)] is being served.
const char * hour_to_meal_map[] = {
    NO_MEALS,
    NO_MEALS,
    NO_MEALS,
    NO_MEALS,
    NO_MEALS,
    BREAKFAST, // i = 5
    BREAKFAST,
    BREAKFAST,
    BREAKFAST,
    BREAKFAST,
    BREAKFAST,
    LUNCH, // i = 11
    LUNCH,
    LUNCH,
    LUNCH,
    LUNCH,
    LUNCH,
    DINNER, // i = 17
    DINNER,
    DINNER,
    DINNER,
    DINNER,
    DINNER,
    DINNER // i = 23
};

// Returns a boolean for whether the two hours have the same meal being eaten.
int same_meal(size_t hour_one, size_t hour_two) {
    return hour_to_meal_map[hour_one] == hour_to_meal_map[hour_two];
}

(至于为什么要将 hour_to_meal_map 映射到字符串而不是索引,这是任何人的猜测。但我正在做一个以这种方式设置的项目。)

我是否正确,这在这里是合法的,重要的是每个值只有一个地方被写为文字? (#define NO_MEALS "none" 被刻意回避!!)

如果这段代码在头文件中,那没有什么区别,不是吗? (我希望标准要求meals 在每个编译单元中具有相同的值?)。

我发现很多初学者的问题都在询问很明显他们应该使用strcmp 的案例,但我找不到一个可以回答这个特殊案例的案例。任何帮助将不胜感激,特别是如果您能指出 C 标准的正确部分,以便我可以真的确定我理解所有的细微之处。

【问题讨论】:

  • 在这种情况下enum 可能更有用。
  • 在实现这些时您有什么问题吗? C 标准也可以用实际代码进行测试。您还可以测试您的实际实现,而不是阅读它应该是什么。
  • 不,到目前为止还没有遇到任何问题。只是想确保我没有滥用 C 标准(并且想创建一个答案不是“使用 strcmp !!”的问题,就像我发现的所有其他人一样!)。实际代码与此极为相似。
  • @dvhh 我同意;如果可能的话,我的目标基本上是在已经编写好的程序的结构内工作。
  • 我不认为初始化是合法的。一些 gcc 版本接受它,但我确定它不是可移植的; meals[0] 不是常量表达式(在 C 中),静态初始化器必须是所有常量。

标签: c string-literals


【解决方案1】:

将两个相同类型的字符串与==!= 进行比较始终是合法的。这在C standard 的第 6.5.9 节中有详细说明,其中详细说明了平等运算符:

2 应满足以下条件之一:

  • 两个操作数都有算术类型;
  • 两个操作数都是指向兼容类型的合格或不合格版本的指针;
  • 一个操作数是一个指向对象类型的指针,另一个是一个指向void的合格或非合格版本的指针;或者
  • 一个操作数是一个指针,另一个是一个空指针常量。

...

4 两个指针比较相等当且仅当两个指针都是空指针,都是指向同一个对象的指针(包括指向一个对象的指针) 和开头的子对象)或函数,两者都是指向 一个超过同一数组对象的最后一个元素,或者一个是 指向一个数组对象末尾的指针,另一个是 指向恰好发生在的不同数组对象的开头的指针 紧跟地址空间中的第一个数组对象

在这种情况下,您有一个指针数组,并且您将其中一个指针的值分配给另一个数组。因此,如果您比较两个指针并且它们都包含(例如)meals[0] 的值,例如字符串常量“none”的地址,保证它们比较相等。

您需要注意的是,如果给定的字符串常量在多个地方使用。在这种情况下,它们不一定相同。

例如,给定这个:

const char *s1 = "test";
const char *s2 = "test";

s1s2 的值不能保证相同,因为这两个字符串常量可以彼此不同,尽管编译器可能会选择使它们相同。这不同于:

const char *s1 = "test";
const char *s2 = s1;

s1s2 相同,这反映了您的情况。

正如您所提到的,hour_to_meal_map 包含数字常量(最好是 enum 的成员)并且这些常量随后映射到字符串数组会更有意义。但是指向字符串常量的指针实际上就是这样。

【讨论】:

  • 更糟糕的是,对于两个 "test" 文字,编译器可能会使用称为“字符串池”的优化功能,实际上只存储其中一个,从而给出相同的地址到s1s2 两者。如果这符合 C 标准是另一回事,但这就是优化器的工作量。也许将两个地址相互比较会阻止优化,在这种情况下,比较对于内存消耗来说是一件坏事。
  • @Lundin 我已经知道那个答案了。标准允许编译器进行字符串池(或不允许)。所以s1 == s2 绝对可以返回真或假。不过,这不是未定义的行为,所以这是一个“合法”的比较。尽管是“合法”比较,但我认为这是无用的,所以我希望我的问题是关于何时可以进行此类比较并期望它有用(我'不认为预检查是有用的,因为它更像是一个小的优化)。
【解决方案2】:

我能想到几个char * 相等有意义的情况:

  1. 您给出的案例:从同一个指针复制
  2. 对于大多数(所有?)编译器:在同一翻译单元中使用任何相同值的字符串文字。这是一种极其常见的优化,无论如何都可以轻松测试。
  3. 如果您通过intern() 函数显式传递字符串
  4. 在执行昂贵的值检查之前进行快速短路比较

【讨论】:

  • 什么是“intern() 函数”?
  • @MM 一个返回具有给定值的唯一标识字符串的函数。使用任何类似哈希表的数据结构来实现都是微不足道的。
猜你喜欢
  • 1970-01-01
  • 2011-11-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多