如何在C语言中最好地使用const关键字?

18

我试图了解在C代码中该如何使用const关键字。一开始我并没有太在意这个,但是后来我看到有很多例子都在大量使用const关键字。那我是不是应该去努力回头将合适的变量都设为const?还是说这样只是浪费时间呢?

我认为使用const可以使代码更易读,尤其是在函数调用时,它能让人和编译器都更清楚哪些变量会被修改。除此之外,我是否忽略了其他重要的点呢?


1
无论如何,一切都是变量。const只是一个提示,指定的值不应更改。但是,您可以通过指针等方式来操纵值,从而在编译器背后更改该值。参考:https://dev59.com/EG865IYBdhLWcg3wduaH - Marc B
相关链接:说服我使用const正确性(这是关于C++的,但同样适用于C)。 - netcoder
我不会去破坏那些工作正常且你已经完成的应用程序。然而,如果你有一些库,将它们使用 const 也无妨。但在某些时候,你可能必须放弃一些东西。例如,const 和多维数组真的很难协同工作。 - Shahbaz
是的,你说得完全正确,这与可读性有关。正如@Mark B所说,如果有人想要通过后门更改const的值,那么这很容易实现,因此你只是在传达你的意图。由于C语言非常简洁,因此任何可以帮助提高可读性的东西都是好的。 - PeteH
在C++中有两种非常不同的const性质,不应混淆。一个const对象是一个无法通过任何方式修改的对象。一个const指针或引用只是一个指针或引用,不能直接使用来修改对象,但对象仍然可以通过其他方式进行修改。 - David Schwartz
1
C++中的'const'声明:为什么和如何 - Grijesh Chauhan
4个回答

15

const是有类型的,#define宏没有。

const受C块的限定,#define适用于一个文件(或更严格地说,编译单元)。

const在参数传递中最有用。如果您看到带指针的原型使用了const,则知道安全地传递数组或结构体,因为函数将不会更改它。没有const就可以更改。

查看strcpy()等定义,就会明白我的意思。在一开始将“const-ness”应用于函数原型。回溯添加const并不那么困难,但是需要“很多工作”(但如果按小时计费,则可以)。

还要考虑:

const char *s = "Hello World";
char *s = "Hello World";

哪一个是正确的,为什么?


所以在你的例子中,我会认为我无法更改第一行中的“Hello World”部分(但我可以使s指向完全不同的字符串),这正确吗?我也在这里和那里看到了那种表示法,这是初始化字符串(即char数组)的方式吗? - c00kiemonster
如果内容可以被修改,就不要把它声明为 const。但在上面的例子中,s 是一个常量,因此无法修改。 - David Schwartz
1
@c00kiemonster:是的,第一个例子,使用const是正确的。第二个例子是错误的,但不幸的是很常见。它会导致尝试更改只读内存(崩溃!)。很久以前,Visual Studio 5允许这样做,并产生了一些“有趣”的效果。 - cdarke
@cdarke 不,第二个例子很遗憾是正确的,但实现是有定义的...我希望有一天C标准会改变这个事实,因为99%的编译器不允许它。我仍然不明白为什么编译器在将字符串字面量修改定义为未定义行为时不产生警告。 - Stargateur
@Stargateur:我想这取决于你所说的“正确”是什么意思。 - cdarke

7

如何在C语言中最好地使用const关键字?

当您想要将其设置为“只读”时,请使用const。就是这么简单 :)


3

使用const 不仅是一种良好的实践,而且可以提高代码的可读性和理解性,还有助于防止一些常见的错误。在适当的情况下,一定要使用const


2
除了在试图修改常量并将常量作为非const参数传递时产生编译器错误以充当编译器保护外,它还使编译器能够执行某些优化,因为它知道该值不会改变,因此可以缓存该值而不必从内存中重新读取,因为它不会发生变化,并且它允许立即在代码中进行替换。
C const
const和register基本上是volatile的相反,使用volatile将覆盖文件和块范围内的const优化以及块范围内的register优化。const register和register将产生相同的输出,因为在gcc C -O0上,const在块范围内不起作用,并且在-O1及以上版本中是多余的,因此仅适用于register优化在-O0中,并且从-O1开始就变得多余。
#include<stdio.h>

int main() {
    const int i = 1;
    printf("%d", i);
}

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov DWORD PTR [rbp-4], 1
  mov eax, DWORD PTR [rbp-4] //load from stack isn't eliminated for block-scope consts on gcc C unlike on gcc C++ and clang C, even though value will be the same
  mov esi, eax
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  leave
  ret

在这种情况下,使用 -O0 时,constvolatileauto 都会产生相同的代码,只有 register 不同。c.f.
#include<stdio.h>
const int i = 1;
int main() {
    printf("%d", i);
}

i:
  .long 1
.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  mov eax, DWORD PTR i[rip] //load from memory
  mov esi, eax
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  pop rbp
  ret

使用 const int i = 1; 来代替:
i:
  .long 1
.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  mov eax, 1  //saves load from memory, now immediate
  mov esi, eax
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  pop rbp
  ret

C++ const (常量)
#include <iostream>

int main() {
    int i = 1;
    std::cout << i;
}

main:
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov DWORD PTR [rbp-4], 1 //stores on stack
  mov eax, DWORD PTR [rbp-4] //loads the value stored on the stack
  mov esi, eax
  mov edi, OFFSET FLAT:_ZSt4cout
  call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
  mov eax, 0
  leave
  ret

#include <iostream>

int main() {
    const int i = 1;
    std::cout << i;
}

main:
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov DWORD PTR [rbp-4], 1 //stores it on the stack
  mov esi, 1               //but saves a load from memory here, unlike on C
                           //'register' would skip this store on the stack altogether
  mov edi, OFFSET FLAT:_ZSt4cout
  call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
  mov eax, 0
  leave
  ret

#include <iostream>
int i = 1;
int main() {

    std::cout << i;
    }

i:
  .long 1
main:
  push rbp
  mov rbp, rsp
  mov eax, DWORD PTR i[rip] //load from memory
  mov esi, eax
  mov edi, OFFSET FLAT:_ZSt4cout
  call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
  mov eax, 0
  pop rbp
  ret

#include <iostream>
const int i = 1;
int main() {

    std::cout << i;
    }

main:
  push rbp
  mov rbp, rsp
  mov esi, 1 //eliminated load from memory, now immediate
  mov edi, OFFSET FLAT:_ZSt4cout
  call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
  mov eax, 0
  pop rbp
  ret

C++有一个额外的限制,即如果未初始化const(在文件范围和块范围内)则会产生编译器错误。在C ++中,默认情况下,const也具有内部链接。 volatile仍然覆盖constregister,但const register在C ++上结合了两种优化。
尽管所有上述代码都使用默认的隐式-O0编译,但是当使用-Ofast编译时,在clang或gcc上对于文件范围的consts,令人惊讶地发现const仍然不是冗余的。除非使用const,否则内存加载不会被优化掉,即使文件范围变量在代码中没有被修改。https://godbolt.org/z/PhDdxk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接