Why is C having so many different types?

I am writing a simple timer function to calculate the elapsed time between start and end

 double mytimer(struct timeval *start, struct timeval *end) { return (end->tv_sec - start->tv_sec) + (end->tv_usec - start->tv_usec)*1e-6; } 

gcc gives the following warnings:

warning: converting to 'double from' __suseconds_t may change its value
warning: converting to 'double from' __time_t may change its value

Here is the definition of timeval:

 struct timeval { time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ }; 

So my question is: why does C define so many incompatible types instead of just using primitive types like int short ...? This is not very convenient. And how can I perform arithmetic operations on these types?

Update

Most of you seemed to neglect my second question. What is the standard way to add two different types, such as time_t and suseconds_t ?

+5
c type-conversion
source share
7 answers

Because the contents of time_t etc. contains an implementation, there is nothing saying that they should contain a few seconds as an integer, as the comment in the code does. The reason is that they want these types to be portable between different systems.

In practice, time.h really quite cumbersome, so most time programs end up calling system-specific functions.

+7
source share

How the butterfly correctly highlights, the purpose of the C ++ language and the standard library design is to ensure logical correctness at compile time.

This means that we are striving for a situation where a program that does the wrong thing in certain obscure circumstances simply will not compile (or will compile, but will warn you).

This means that you can fix these logic errors before your software ever runs in a test harness, not to mention your clients.

The result of this is that correctly written C ++ code in most cases can be “correct” before running, which means that you spend much less time tracking obscure errors that you would otherwise want.

The magic of C ++ is what accomplishes this incredible feat, offering fantastic efficiency and code optimization.

Note: “right” I mean that he will reliably do what you think is telling you. You don't care to write the right logic!

regarding questions:

So my question is: why does C define so many incompatible types instead of just using primitive types like int short ...? This is not user friendly.

They become incompatible in order to deliberately prevent you from converting them into each other. They represent different concepts, just like speed and distance are different concepts. There is no direct conversion between them.

And how can I do arithmetic operations on these types?

do intermediate results of your arithmetic that these types can safely convert without losing accuracy. In this case, tv_sec and tv_usec are integer types, therefore, even if they are incompatible with each other, they are both converted to double separately.

eg:

 double mytimer(struct timeval *start, struct timeval *end) { return double(end->tv_sec - start->tv_sec) + double(end->tv_usec - start->tv_usec) * 1e-6; } 
+4
source share

The reason is because built-in types like int are platform dependent. Thus, on one computer, int may be good enough to store a time value, while on another you need a long one. In order for users to write programs that run on all platforms, types such as time_t were usually just aliases for some primitive type suitable for that particular platform. It actually requires a bit more training effort in the beginning, but in the long run, these efforts will pay big dividends.

It makes sense, right?

[EDIT]: Regarding strange warnings: the compiler warns that converting time_t and suseconds_t to double may lose some information. This is because both types are integer types with more bits than the double mantissa part. In your case, this will only apply to very large time values, so you can simply ignore these warnings. (But how does the compiler know that the time_t value usually fits into double? Therefore, it emits this warning.) Actually, you cannot do this without making the code platform dependent.

+2
source share

To avoid warnings

 double mytimer(struct timeval * start, struct timeval * end) { long usec = end->tv_usec - start->tv_usec; long sec = end->tv_sec - start->tv_sec; return 1.0 * sec + 1e-6 * usec; } 

If you see data types defined in <sys/types.h> , essentially you will find

 typedef long time_t; ... typedef long suseconds_t; 

There are no special format specifiers for struct timeval , but structure elements are of type long. Hope this solves your problem.

+2
source share

[NB I almost completely rewrote this answer since I first posted it.]

The answer to your first question: C has so many types to balance the needs of supporting machines of all different word sizes with reasonable portability. Things get complicated because of the desire to also support specialized quantities, such as "data structure sizes", "file offsets" and "real-time times", are reasonably portable, and although sometimes these specialized quantities end up not determined by the language specification or a compiler, but rather an operating base.

In general, there are two problems when converting from a large integral type to a floating point type:

  • floating point type cannot accurately represent all significant digits of integral type

  • a floating point type may not even be able to handle an integral type range

In the case of time_t , an additional problem occurs:

  1. The time_t type may not be an integer number of seconds, which you can significantly subtract

(These days, “useful” compiler warnings in response to these problems sometimes seem bordering on nannyish, and I share your concern. It may be difficult to understand in what obscure situation the compiler is really worried about, and it may be difficult to understand how to rewrite the code without warnings, and it can be difficult to make sure that any throws you need to insert do not ultimately make the code even less secure.)

If you are not worried about problem # 3 (if you want to assume that time_t is an integer number of seconds), you can minimize the likelihood of data loss by first subtracting (and into an integral type) and then converting:

 return (sometype)(end->tv_sec - start->tv_sec) + (sometype)(end->tv_usec - start->tv_usec) / 1e6; 

But, of course, the big question is: what type should be?

I believe that your best bet is to throw a double in each of these places. Both the guaranteed range and the accuracy of the double type in C are quite large. Therefore, if you do not manipulate time differences exceeding 1e50 years (and if someone did not use the subsec_t type as a 266-bit type or something else), your code should be safe even when suppressing warnings, and you can insert a comment to to this issue.

If you want to understand how these problems can manifest themselves in practice, they are easy to demonstrate. Try the following:

 float f = (float)2000000123L - (float)2000000000L; printf("%f\n", f); 

If you have a 64-bit compiler, you can observe precision loss even with double precision:

 double d = (double)9000000000000001234LL - (double)9000000000000000000LL; printf("%f\n", d); 

On my machine, these two fragments print 128 and 1024 respectively.

I'm not sure what exactly of the three questions your compiler tried to warn you about. # 1 is the most likely opportunity. And you can see how the loss of accuracy disappears if you convert after subtraction, and not earlier:

 f = 2000000123L - 2000000000L; d = 9000000000000001234LL - 9000000000000000000LL; 

or

 f = (float)(2000000123L - 2000000000L); d = (double)(9000000000000001234LL - 9000000000000000000LL); 

Back when all we had was 32-bit longs and 64-bit doubles, in practice this was not very important (since the IEEE 754 double has something like 52 bits of precision). Now that 64-bit types are becoming commonplace, these kinds of warnings are becoming more widespread. If you are satisfied that the double type has sufficient precision in all your time subtracting needs, you can use the appropriate double casts to turn off warnings. (And, again, here by “appropriate” we mean “after subtraction.”) If you want to be even more secure, you can go instead of long double . If your compiler supports it, and if it is really "longer" than a regular double , it can really reduce the problem of accuracy loss. (Here is the previous example using long double :

 long double ld = (long double)9000000000000001234LL - (long double)9000000000000000000LL; printf("%Lf\n", ld); 

On my system, this prints 1234 )

But with all that has been said, in this case, if you want to make your life easier and, by the way, turn to problem No. 3 at the same time, you could and perhaps should use the standard function to calculate the difference. The standard function for subtracting two time_t values ​​is difftime . (It’s a difftime job to worry about all these things, including the possibility that time_t does not represent seconds directly.) That way you could write

 return difftime(end->tv_sec - start->tv_sec) + (double)(end->tv_usec - start->tv_usec) / 1e6; 

although, of course, the problem is with subseconds.

The best solution is a pre-written library function for subtracting two timeval , and you can spend some time looking for one of them.

+2
source share

The reason is called type safety. Not all types of expressions make sense in a work program. And security type means that the compiler refuses to allow unsafe, invalid or improper operations.

Ultimately, a compiler that rejects such bad code will save the programmer’s efforts, as it takes the programmer more time to detect the problem without help than the compiler.

C and C ++ have a number of security types. Other programming languages ​​have more, others less. It just means that some programming styles are more effective in different programming languages.

Warnings are a slightly smaller prescriptive form of type safety — they force compilers to warn about suspicious things, rather than completely reject them.

0
source share

Although these types are usually implemented with standard integral types, you cannot consider this for portability. The standard library offers some convenient functions for conversions, etc. For example, if you want to calculate the delay between the moments, you can use difftime , which returns double .

0
source share