How do I best use the const keyword in C?

孤街浪徒 提交于 2020-05-28 13:04:08

问题


I am trying to get a sense of how I should use const in C code. First I didn't really bother using it, but then I saw a quite a few examples of const being used throughout. Should I make an effort and go back and religiously make suitable variables const? Or will I just be waisting my time?

I suppose it makes it easier to read which variables that are expected to change, especially in function calls, both for humans and the compiler. Am I missing any other important points?


回答1:


const is typed, #define macros are not.

const is scoped by C block, #define applies to a file (or more strictly, a compilation unit).

const is most useful with parameter passing. If you see const used on a prototype with pointers, you know it is safe to pass your array or struct because the function will not alter it. No const and it can.

Look at the definition for such as strcpy() and you will see what I mean. Apply "const-ness" to function prototypes at the outset. Retro-fitting const is not so much difficult as "a lot of work" (but OK if you get paid by the hour).

Also consider:

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

which is correct, and why?




回答2:


How do I best use the const keyword in C?

Use const when you want to make it "read-only". It's that simple :)




回答3:


Using const is not only a good practice but improves the readability and comprehensibility of the code as well as helps prevent some common errors. Definitely do use const where appropriate.




回答4:


Apart from producing a compiler error when attempting to modify the constant and passing the constant as a non-const parameter, therefore acting as a compiler guard, it also enables the compiler to perform certain optimisations knowing that the value will not change and therefore it can cache the value and not have to read it fresh from memory, because it won't have changed, and it allows it to be immediately substituted in the code.

C const

const and register are basically the opposite of volatile and using volatile will override the const optimisations at file and block scope and the register optimisations at block-scope. const register and register will produce identical outputs because const does nothing on C at block-scope on gcc C -O0, and is redundant on -O1 and onwards, so only the register optimisations apply at -O0, and are redundant from -O1 onwards.

#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

In this instance, with -O0, const, volatile and auto all produce the same code, with only register differing 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

with const int i = 1; instead:

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++ has the extra restriction of producing a compiler error if a const is not initialised (both at file-scope and block-scope). const also has internal linkage as a default on C++. volatile still overrides const and register but const register combines both optimisations on C++.

Even though all the above code is compiled using the default implicit -O0, when compiled with -Ofast, const surprisingly still isn't redundant on C or C++ on clang or gcc for file-scoped consts. The load from memory isn't optimised out unless const is used, even if the file-scope variable isn't modified in the code. https://godbolt.org/z/PhDdxk.



来源:https://stackoverflow.com/questions/14401856/how-do-i-best-use-the-const-keyword-in-c

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!