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.