C字符串长度 – 这是有效的代码吗?
这种类型的表达式在C中是否有效(在所有编译器上)? 如果是的话,这是好的C吗?
char cloneString (char clone[], char string[], int Length) { if(!Length) Length=64 ; int i = 0 ; while((clone[i++] = string[i]) != '\0', --Length) ; clone[i] = '\0'; printf("cloneString = %s\n", clone); };
这会更好,更糟,无动于衷吗?
char *cloneString (char clone[], char string[], int Length) { if(!Length) Length=STRING_LENGTH ; char *r = clone ; while ( //(clone[i++] = string[i]) != '\0' *clone++ = *string++ , --Length ); *clone = '\0'; return clone = r ; printf("cloneString = %s\n", clone); };
Stackoverflow希望我为这个问题添加更多文字!
好的! 我很担心
a。)表达式,例如c ==(a = b)b。)索引与指针之间的性能
任何意见?
非常感谢。
是的,它在语法上对所有编译器都有效(虽然在语义上无效),不,它不被认为是好的C.大多数开发人员会同意逗号运算符是坏事,并且大多数开发人员通常会同意一行代码应该只做一件特定的事情。 while
循环执行整整四个并且具有未定义的行为:
- 它递增
i
; - 它将
string[i]
分配给clone[i++]
; ( 未定义的行为 :你应该在一个递增/递减它的语句中只使用i
一次) - 它检查
string[i]
是否为0( 但丢弃比较结果 ); - 它递减
Length
,并在递减后延长循环,如果Length == 0
。
更不用说假设如果没有提供Length
为64是一个可怕的想法,并留下足够的空间来更容易被利用来破坏或破解程序的未定义行为。
我看到你自己写的并且你关注性能,这显然是你把所有东西都放在一起的原因。 别。 通过一起压缩语句来缩短代码并不比代码更长,因为语句没有被挤压在一起。 它仍然可以执行相同数量的操作。 在你的情况下,你通过挤压一起来引入错误。
代码具有未定义的行为:
表达方式
(clone[i++] = string[i])
两者都以不顺序的方式从两个不同的子表达式修改和访问对象i
,这是不允许的。 编译器可能在string[i]
使用i
的旧值,或者可能使用i
的新值,或者可能执行完全不同且意外的操作。
简单的答案没有。
- 为什么返回char并且函数没有return语句?
- 为什么64?
- 我假设这两个数组的长度是
Length
– 添加文档来说明这一点。 - 为什么 ; 在新的一行而不是在声明之后?
…
好的,所以我决定将我的评论发展成一个实际的答案。 虽然这并没有解决你问题中的特定代码,但它会解决潜在的问题,我认为你会发现它很有启发性,因为你可以使用它 – 让我们称它为指南 – 用于你的一般编程。
我所提倡的,特别是如果你只是学习编程,那就是专注于可读性,而不是你认为或被告知提高速度/性能的小噱头。
我们举一个简单的例子。 迭代C
的向量(不是在C++
)的惯用方法是使用索引:
int i; for (i = 0; i < size; ++i) { v[i] = /* code */; }
有人告诉我,当我开始编程时, v[i]
实际上被计算为*(v + i)
因此在生成的汇编程序中,这会被分解(请注意,此讨论已简化):
- 将
i
与sizeof(int)
相乘 - 将该结果添加到
v
的地址 - 访问此计算地址处的元素
所以基本上你有3个操作。
让我们将其与通过指针访问进行比较:
int *p; for (p = v; p != v + size; ++p) { *p = /*..*/; }
这样做的优点是*p
实际上只扩展为一条指令:
- 访问地址
p
处的元素。
2个额外的指令不会很多,但是如果你的程序花费大部分时间在这个循环中(非常大的size
或多次调用(包含这个的函数)循环)你意识到第二个版本使你的程序快3倍。 那是很多。 因此,如果您在我开始时就像我一样,您将选择第二个变体。 别!
因此第一个版本具有可读性(您明确地描述了您访问向量v
第i
个元素),第二个版本使用噱头而不利于可读性(您说您访问了内存位置)。 现在这可能不是不可读代码的最佳示例,但原则是有效的。
那么为什么我要告诉你使用第一个版本:在你牢牢掌握缓存,分支,归纳变量(以及更多)等概念以及它们如何应用于现实编译器和程序性能之前,你应该清楚这些噱头并依靠编译器来进行优化。 它们非常智能,并且将为两种变体生成相同的代码(当然,启用了优化)。 因此,第二种变体实际上只是因为可读性而与第一种变体在性能方面相同。
另一个例子:
const char * str = "Some string" int i; // variant 1: for (i = 0; i < strlen(str); ++i) { // code } // variant 2: int l = strlen(str); for (i = 0; i < l; ++i) { // code }
自然的方式是编写第一个变体。 您可能认为第二个提高了性能,因为您在循环的每次迭代中调用函数strlen
。 而且你知道获取字符串的长度意味着遍历所有字符串直到你到达结尾。 所以基本上调用strlen
意味着添加一个内部循环。 哎哟,这必须减慢程序。 不一定:编译器可以优化调出,因为它总是产生相同的结果。 实际上,当您引入一个新变量时,您可能会受到伤害,该变量必须从一个非常有限的注册池中分配一个不同的寄存器(一个极端的例子,但不过这里要点)。
直到很久以后,不要把精力放在这样的事情上。
让我向您展示一些其他内容,它将进一步说明您对性能所做的任何假设很可能是错误的和误导性的(我不是要告诉您,您是一个糟糕的程序员 - 远非它 - 就像您一样学习 ,你应该将你的精力投入到除表现之外的其他方面):
让我们乘以两个矩阵:
for (k = 0; k < n; ++k) { for (i = 0; i < n; ++i) { for (j = 0; j < n; ++j) { r[i][j] += a[i][k] * b[k][j]; } } }
与
for (k = 0; k < n; ++k) { for (j = 0; j < n; ++j) { for (i = 0; i < n; ++i) { r[i][j] += a[i][k] * b[k][j]; } } }
两者之间的唯一区别是操作执行的顺序。 它们是完全相同的操作(数量,种类和操作数),只是以不同的顺序。 结果是等价的(加法是可交换的)所以在纸上它们应该花费大量的时间来执行。 在实践中,即使启用了优化(一些非常聪明的编译器可以重新排序循环),第二个示例可能比第一个示例慢2-3倍。 甚至第一个变体距离最佳(速度方面)还有很长的路要走。
所以基本点 : 担心UB正如其他答案所示, 不要担心现阶段的表现 。
第二块代码更好。
这条线
printf("cloneString = %s\n", clone);
因为之前有一个返回声明,所以永远不会被执行。
为了使您的代码更具可读性,请进行更改
while ( *clone++ = *string++ , --Length );
至
while ( Length > 0 ) { *clone++ = *string++; --Length; }
这可能是解决您问题的更好方法:
#include #include void cloneString(char *clone, char *string) { for (int i = 0; i != strlen(string); i++) clone[i] = string[i]; printf("Clone string: %s\n", clone); }
话虽如此,已经有了标准function:
strncpy(const char *dest, const char *source, int n)
dest是目标字符串,source是必须复制的字符串。 此function最多可复制n个字符。
所以,你的代码将是:
#include #include void cloneString(char *clone, char *string) { strncpy(clone, string, strlen(string)); printf("Clone string: %s\n", clone); }