📜 ⬆️ ⬇️

Calculate the circumference

"Please write a C ++ function that gets the diameter of a circle as a float and returns the circumference as a float."

Sounds like a job in the first week of a C ++ course. But this is only at first glance. Difficulties arise already at the first stages of solving the problem. I propose to consider several approaches.

Student: How's that option?
')
#include <math.h> float CalcCircumference1(float d) { return d * M_PI; } 

Instructor: Yes, this code can compile normally. Or maybe not. M_PI is not defined in C or C ++ standards. With the compiler in VC ++ 2005, this will work, but for later versions you will have to use #define _USE_MATH_DEFINES before turning on math.h to get access to this non-standard constant. And as a result, you will write code that other compilers can not cope with.

Scene Two


Student: Thank you for your wisdom, teacher. I removed the dependency on the non-standard constant M_PI. That's better?

 float CalcCircumference2(float d) { return d * 3.14159265358979323846; } 

Instructor: Yes, that's better. This code will be compiled, and you will get the desired result. But your code is ineffective. You multiply the number with single precision by a constant with double precision. The compiler will have to cast the float function parameter to a double type, and then perform the inverse transform to get the return value. If you compile code for SSE2, this adds two instructions to the dependency chain and the calculations can be done three times longer! In most cases, such delays are quite acceptable, but in the internal cycle the negative effect can be very significant.

If you compile for the x87 platform, then converting to the double type costs nothing, but converting back is expensive — so expensive that some optimizing compilers throw out the conversion, and as a result you can get EXTREMELY UNEXPECTED results — for example, CalcCircumference (r) == CalcCircumference (r) returns false!

Scene Three


Student: Thank you, teacher. Honestly, I do not know what SSE2 and x87 are, but I see how elegant the code becomes when the types are consistent. This is real poetry. I will use a single precision constant. How do you like this?

 float CalcCircumference3(float d) { return d * 3.14159265358979323846f; } 

Instructor: Yes, excellent! The “f” at the end of the constant changes everything. If you looked at the generated machine code, you would understand that this option is much more compact and efficient. However, I have comments on the style. Don't you think that this mysterious constant has no place inside the function? Even if this number is Pi, the value of which is unlikely to change, it is better to give the constant a name and place it in the header file.

Scene Four


Student: Thank you. You explain everything very intelligibly. I will put the line of code below in the general header file and use it in my function. So ok?

 const float pi = 3.14159265358979323846f; 

Instructor: Yes, great! Using the keyword "const" you indicated that the variable should not and cannot be changed, moreover, it can now be placed in the header file. But, I'm afraid, now we have to delve into some subtleties of defining scopes in C ++.

By declaring pi with the const keyword, you will get the effect of the static keyword as a bonus. For integer types, this is normal, but if you are dealing with a different data type (floating point number, array, class, structure), the memory for your variable can be allocated separately in each translation unit that includes your header file. In some cases, you will end up with a few dozen or even hundreds of instances of a variable of type float and your executable file will be unreasonably large.

Scene Five


Student: Are you kidding? And what to do?

Instructor: Yes, we are still far from ideal. You can hang the attribute __declspec (selectany) or __attribute __ (weak) on the declaration of a constant so that VC ++ and GCC, respectively, understand that it is enough to save one of numerous copies of this constant. But since we are in the idealistic world of science, I insist on the use of standard C ++ constructs.

Scene Six


Student: So something like this? Using constexpr from C ++ 11?

 constexpr float pi = 3.14159265358979323846f; 

Instructor: Yes. Now your code is perfect. Of course, VS 2013 will not be able to compile it, because it does not know what to do with constexpr. But you can always use the Visual C ++ Compiler Nov 2013 CTP toolkit or the latest version of GCC or Clang.

Student: Can #define be used?

Instructor: No!

Student: Oh, to hell with all this! I'd rather be a barista .

Scene Seven


Student: Stop, I remember something. It's easy! Here's what the code will look like:

 mymath.h: extern const float pi; mymath.cpp: extern const float pi = 3.14159265358979323846f; 

Instructor: Exactly, in most cases this will be the right decision. But what if you are working on a DLL, how will the external functions access mymath.h in your DLL? In this case, you will have to export and import this symbol.

The problem is that the rules for integer types are completely different. It is advisable and recommended to add the following to the C ++ header file:

 const int pi_i = 3; 

The number of Pi here is indicated not accurately enough, but the fact is that the integer constants in the header files do not require memory allocation, unlike other constants. What makes this difference is not entirely clear, but more often than not it doesn’t matter.

About what “static” in “const” means, I found out several years ago when I was asked to find out why one of our key DLL libraries suddenly gained 2 MB in weight. It turns out that in the header file there was an array of constants, and we received thirty copies of this array in a DLL. That is, sometimes it does matter.

And yes, I still believe that #define is a terrible choice in this case. Maybe this is not the worst solution, but I do not like it at all. I once encountered a compilation error caused by a pi declaration with #define. Enough little, I tell you! Namespace littering is the main reason why #define should be avoided as much as possible.

Conclusion


I do not know exactly what lesson we have learned from all this. The essence of the problem that arises when we declare a constant of type float or double in a header file or a structure or array of constants is far from clear to everyone. In most serious programs, duplicates of static constants arise because of this, and sometimes they are unjustifiably large. I think constexpr can save us from this problem, but I do not have enough experience to use it to know for sure.

I came across programs that were hundreds of kilobytes larger than their “real” size — all because of an array of constants in the header file. I also saw a program that ultimately contained 50 copies of a class object (plus another 50 calls of constructors and destructors), because this class object was defined as the type const in the header file. In other words, there is something to think about.

You can see how this happens with GCC by downloading a test program from here . Build it with the make command, and then run the objdump -d constfloat | grep flds to find four instructions to read from adjacent addresses in a data segment. If you want to occupy more space, add the following to header.h:

 const float sinTable[1024] = { 0.0, 0.1, }; 

In the case of GCC, the increment will be 4 KB for one conversion entry (source file), that is, the executable file will grow by 20 KiB, even if no one ever accesses the table.

As usual, operations on floating-point numbers are associated with considerable difficulties, but in this case, it seems to me, the very slow evolution of C ++ is to blame.

What else to read on:


VC ++: how to avoid duplication and how to understand that duplication cannot be avoided

The compiler in VC ++ 2013 Update 2 has the / Gw option, which places each global variable in a separate COMDAT container, allowing the linker to detect and dispose of duplicates. Sometimes this approach helps to avoid the negative effects of declaring constants and static variables in header files. In Chrome, these changes helped save about 600 KB ( details ). Part of this savings was achieved (surprise!) By removing thousands of copies of twoPiDouble and piDouble (as well as twoPiFloat and piFloat).

However, in VC ++ 2013 STL there are several objects declared as static or const in the class declaration that / Gw cannot delete. All of these objects occupy one byte, but in the end runs over 45 kilobytes. I informed the developers about this error and received an answer that it was fixed in VC ++ 2015.

At the request of the author share the link to the original here.

Source: https://habr.com/ru/post/252871/


All Articles