Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

关于 const 与常量的关系存在的错误与遗漏 #5

Closed
frederick-vs-ja opened this issue Mar 6, 2020 · 18 comments
Closed

关于 const 与常量的关系存在的错误与遗漏 #5

frederick-vs-ja opened this issue Mar 6, 2020 · 18 comments

Comments

@frederick-vs-ja
Copy link

页面链接

const常量与#define宏定义常量的区别:const常量具有类型,编译器可以进行安全检查;#define宏定义没有数据类型,只是简单的字符串替换,不能进行安全检查。

此处的错误在于 #define 定义的宏常量(假设它不是花括号初始化器列表)同样存在类型。
例如若写 #define FOURTY_TWO 42 ,则 FOURTY_TWO 的类型是 int 。具体的类型和各种字面量(整数、浮点、用户定义等)和运算符的结果类型有关。

const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。

此处的错误在于,若用 const 定义常量(类型为整数或枚举,必须以常量表达式初始化),则这种常量在非 odr 式使用(粗略来说是只使用其值)时不需要依赖其身为变量的身份,一定场合下甚至可以不需要定义(譬如作为类的 static 成员对象)。
编译器在作为常量处理它时,不会依赖“一份定义”,而是像是立即数一样使用它,它本身可能在机器码中被“拷贝”到多个地方,和 #define 定义的宏常量的结果相同。
另一方面, const 定义的常量由于是整数或枚举,所以直接变成机器码上的立即数往往性能更好。

最后是遗漏的一点:

const 定义的变量只有类型为整数或枚举,且以常量表达式初始化时才能作为常量表达式。其他情况下它只是一个 const 限定的变量,不要将与常量混淆。

@crazii
Copy link

crazii commented Mar 6, 2020

+1
立即数是指令的一部分, 所以抛开可执行代码所占用的内存, #define的数字常量, 并没有"若干内存拷贝".
退一步说, 如果非要把指令内的立即数作为"若干内存拷贝", 那么const在大部分情况优化后也与其一样. 极少数没有优化或需要取地址的, 那么内存中也会有若干"const的地址拷贝", 内存占用相当. 即便这种情况下, 当然还是#define的立即数快, 它不需要访问额外内存.

@Light-City
Copy link
Owner

页面链接

const常量与#define宏定义常量的区别:const常量具有类型,编译器可以进行安全检查;#define宏定义没有数据类型,只是简单的字符串替换,不能进行安全检查。

此处的错误在于 #define 定义的宏常量(假设它不是花括号初始化器列表)同样存在类型。
例如若写 #define FOURTY_TWO 42 ,则 FOURTY_TWO 的类型是 int 。具体的类型和各种字面量(整数、浮点、用户定义等)和运算符的结果类型有关。

const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。

此处的错误在于,若用 const 定义常量(类型为整数或枚举,必须以常量表达式初始化),则这种常量在非 odr 式使用(粗略来说是只使用其值)时不需要依赖其身为变量的身份,一定场合下甚至可以不需要定义(譬如作为类的 static 成员对象)。
编译器在作为常量处理它时,不会依赖“一份定义”,而是像是立即数一样使用它,它本身可能在机器码中被“拷贝”到多个地方,和 #define 定义的宏常量的结果相同。
另一方面, const 定义的常量由于是整数或枚举,所以直接变成机器码上的立即数往往性能更好。

最后是遗漏的一点:

const 定义的变量只有类型为整数或枚举,且以常量表达式初始化时才能作为常量表达式。其他情况下它只是一个 const 限定的变量,不要将与常量混淆。

感谢指出!文中已修正~

@Light-City
Copy link
Owner

+1
立即数是指令的一部分, 所以抛开可执行代码所占用的内存, #define的数字常量, 并没有"若干内存拷贝".
退一步说, 如果非要把指令内的立即数作为"若干内存拷贝", 那么const在大部分情况优化后也与其一样. 极少数没有优化或需要取地址的, 那么内存中也会有若干"const的地址拷贝", 内存占用相当. 即便这种情况下, 当然还是#define的立即数快, 它不需要访问额外内存.

感谢~

@youthlin
Copy link

借楼发个词语订正: 显示 -> 显式

https://light-city.club/sc/basic_content/const/#3const

最后一段

小结:可以发现未被const修饰的变量不需要 extern 显示 显式 声明!而const常量需要 显示 显式 声明 extern,并且需要做初始化!因为常量在定义后就不能被修改,所以定义时必须初始化。

@Light-City
Copy link
Owner

借楼发个词语订正: 显示 -> 显式

https://light-city.club/sc/basic_content/const/#3const

最后一段

小结:可以发现未被const修饰的变量不需要 extern 显示 显式 声明!而const常量需要 显示 显式 声明 extern,并且需要做初始化!因为常量在定义后就不能被修改,所以定义时必须初始化。

感谢,已修订~

@weihf-007
Copy link

我丢,没看懂,反而更晕了

@CHN-beta
Copy link

请问,这里讨论的“常量表达式”是指 constexpr 的那个常量表达式吗?(尽管并不一定用 constexpr 修饰)

如果是的话,那按照我的理解,常量表达式完全不局限于整数和枚举,浮点也没问题,甚至自己定义一个类都可以(只要满足一定的要求)。标准库中一些类(例如 std::string 或者 std::vector)的对象已经可以用作常量表达式了。

@frederick-vs-ja
Copy link
Author

请问,这里讨论的“常量表达式”是指 constexpr 的那个常量表达式吗?(尽管并不一定用 constexpr 修饰)

如果是的话,那按照我的理解,常量表达式完全不局限于整数和枚举,浮点也没问题,甚至自己定义一个类都可以(只要满足一定的要求)。标准库中一些类(例如 std::string 或者 std::vector)的对象已经可以用作常量表达式了。

其实常量表达式和 constexpr 稍有区别。 C++11 起 constexpr 对象的值确实可以作为常量表达式,按标准说法是该对象可用于常量表达式(usable in constant expressions)。

@ArthurRyan0803
Copy link

常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。
声明为 constexpr 的变量一定是一个常量,而且必须用常量表达式初始化。
出自《C++ Primer》5th

考虑如下代码:

const float a = 1.0;
constexpr float b = a;

constexpr float b = a; 这个地方会有error,原因是变量a仅仅是个浮点常量,而不是常量表达式。想要初始化 constexpr修饰的变量b,必须使用常量表达式,例如:

constexpr float b = 1.23;

constexpr float a = 1.23;
constexpr float b = a;

@xzmqaq0307
Copy link

#define 为啥会多份内存copy啊,预处理阶段进行文本替换。就看替换之后的内容存放的位置,如果立即数替换,没有拷贝的地方,如果是函数替换也没有多份内存拷贝的地方,用得还是原来的东西呀。。

@imkelt
Copy link

imkelt commented Mar 14, 2022

已删

@Thendytx
Copy link

2022年7月23日:github网页上的效果,对此处更改时使用的删除线语法没有生效

@ipengx1029
Copy link

页面链接

const常量与#define宏定义常量的区别:const常量具有类型,编译器可以进行安全检查;#define宏定义没有数据类型,只是简单的字符串替换,不能进行安全检查。

此处的错误在于 #define 定义的宏常量(假设它不是花括号初始化器列表)同样存在类型。 例如若写 #define FOURTY_TWO 42 ,则 FOURTY_TWO 的类型是 int 。具体的类型和各种字面量(整数、浮点、用户定义等)和运算符的结果类型有关。

const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。

此处的错误在于,若用 const 定义常量(类型为整数或枚举,必须以常量表达式初始化),则这种常量在非 odr 式使用(粗略来说是只使用其值)时不需要依赖其身为变量的身份,一定场合下甚至可以不需要定义(譬如作为类的 static 成员对象)。 编译器在作为常量处理它时,不会依赖“一份定义”,而是像是立即数一样使用它,它本身可能在机器码中被“拷贝”到多个地方,和 #define 定义的宏常量的结果相同。 另一方面, const 定义的常量由于是整数或枚举,所以直接变成机器码上的立即数往往性能更好。
最后是遗漏的一点:
const 定义的变量只有类型为整数或枚举,且以常量表达式初始化时才能作为常量表达式。其他情况下它只是一个 const 限定的变量,不要将与常量混淆。

const 定义的变量只有类型为整数或枚举,且以常量表达式初始化时才能作为常量表达式“这句话不对,C++Primer P58提到,用常量表达式初始化的const对象也是常量表达式,所以上面说”const 定义的变量只有类型为整数或枚举“这句话是有问题的,const浮点型变量用常量表达式初始化,也是一个常量表达式。

可是,好像实验下来,const int a = 1; constexpr int b = a; 这个是可以编过的,而int换成float就会提示b不是用常量表达式初始化

@imkelt
Copy link

imkelt commented Aug 11, 2022

页面链接

const常量与#define宏定义常量的区别:const常量具有类型,编译器可以进行安全检查;#define宏定义没有数据类型,只是简单的字符串替换,不能进行安全检查。

此处的错误在于 #define 定义的宏常量(假设它不是花括号初始化器列表)同样存在类型。 例如若写 #define FOURTY_TWO 42 ,则 FOURTY_TWO 的类型是 int 。具体的类型和各种字面量(整数、浮点、用户定义等)和运算符的结果类型有关。

const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。

此处的错误在于,若用 const 定义常量(类型为整数或枚举,必须以常量表达式初始化),则这种常量在非 odr 式使用(粗略来说是只使用其值)时不需要依赖其身为变量的身份,一定场合下甚至可以不需要定义(譬如作为类的 static 成员对象)。 编译器在作为常量处理它时,不会依赖“一份定义”,而是像是立即数一样使用它,它本身可能在机器码中被“拷贝”到多个地方,和 #define 定义的宏常量的结果相同。 另一方面, const 定义的常量由于是整数或枚举,所以直接变成机器码上的立即数往往性能更好。
最后是遗漏的一点:
const 定义的变量只有类型为整数或枚举,且以常量表达式初始化时才能作为常量表达式。其他情况下它只是一个 const 限定的变量,不要将与常量混淆。

const 定义的变量只有类型为整数或枚举,且以常量表达式初始化时才能作为常量表达式“这句话不对,C++Primer P58提到,用常量表达式初始化的const对象也是常量表达式,所以上面说”const 定义的变量只有类型为整数或枚举“这句话是有问题的,const浮点型变量用常量表达式初始化,也是一个常量表达式。

可是,好像实验下来,const int a = 1; constexpr int b = a; 这个是可以编过的,而int换成float就会提示b不是用常量表达式初始化

确实,我的错

@404notfounding
Copy link

define定义的常量在内存中有若干个拷贝怎么理解啊

@heroyang6263
Copy link

define定义的常量在内存中有若干个拷贝怎么理解啊

应该是define定义的常量以立即数的形式存储在指令中,当有一条指令需要使用常量的时候就会对其有一份拷贝,而指令是存储与内存中的

@yycmd
Copy link

yycmd commented Mar 2, 2024

const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝

如何理解这句话?我记得以前看过的内容说#define定义的宏在预处理阶段就替换掉了,运行过程中怎么会出现内存里有若干个拷贝呢

@galaxy-git
Copy link

galaxy-git commented May 3, 2024

const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝

如何理解这句话?我记得以前看过的内容说#define定义的宏在预处理阶段就替换掉了,运行过程中怎么会出现内存里有若干个拷贝呢

学了汇编就知道了。常量是立即数,没有内存地址,是指令的一部分,使用的时候直接使用值,所以是不可访问的,在预编译阶段就确定了,变量是有内存地址的,使用的时候直接用变量名或者地址就行了。还有,const定义的变量如果改变的话在编译器阶段就无法通过,这是和普通变量的区别,使用的时候都是用地址访问值,所以也不存在多份拷贝

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests