5

I write a simple timer function to calculate the time elapsed 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: conversion to ‘double’ from ‘__suseconds_t’ may alter its value
warning: conversion to ‘double’ from ‘__time_t’ may alter 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 simply using primitive types such as int short ...? It's not user-friendly at all.
And how can I do arithmetic operations on these types?

update

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

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
duleshi
  • 1,966
  • 2
  • 21
  • 32
  • `suseconds_t` is likely to be something like `std::size_t` .. or some other unsigned type. – Nawaz Jul 01 '15 at 09:14
  • 15
    Precisely to prevent users from shooting themselves in the foot, as you are happily trying to do. Over the long run, it **is** very user-friendly, because you spend far less time debugging: if your program is incorrect, it won't even compile, and the compiler will tell you why. – user703016 Jul 01 '15 at 09:14
  • 1
    Thankfully you've never heard of languages like C# (.NET) or Haskell :} – user2864740 Jul 01 '15 at 09:17
  • 4
    *"simply using primitive types such as `int` `short` ..."* `int` and `short` aren't necessarily big enough to store the implementation's range of `tv_sec` values. If `tv_sec` is a 64 bit value, isn't it reasonable and potentially useful to warn that you might loose precision in the conversion to `double`? And they're not necessarily *"so many different types"* - they're likely `typedef`s to the simpler, familiar types, but the compiler's showing the `typedef` name as it's typically more informative. – Tony Delroy Jul 01 '15 at 09:23
  • Where does [the C standard](http://www.iso-9899.info/n1570.html) define `suseconds_t`, as an *incomplete type* or otherwise? – autistic Jul 01 '15 at 09:28
  • The warning is not about the addition but about the fact that a `double` can't accurately represent the entire range of `time_t` or `suseconds_t`. – molbdnilo Jul 01 '15 at 10:01
  • C# has a lot more primitive types than C, but they're easy to remember. Pascal, OTOH, has different names for signed and unsigned types. That's not counting constructed types – phuclv Jul 01 '15 at 10:46
  • @molbdnilo Of course I know that. But to add up the two, I have to cast both to a common type first, something like least common multiple. Right? So what's the common supper type of the two types? – duleshi Jul 01 '15 at 12:27
  • Which (version of which) compiler are you using, and what compilation options are you using to get that message? GCC with `-Wconversion` might do it, but modern versions of GCC give a differently formatted error message (which includes the `-Wconversion` or equivalent flag as part of the message). Also, are you compiling for 32-bit or 64-bit platforms? – Jonathan Leffler Jul 01 '15 at 16:02
  • @undefinedbehaviour: the `struct timeval` type is defined by POSIX, not by the C standard. See [`gettimeofday()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/gettimeofday.html) and [``](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_time.h.html#tag_13_64). Note that the function is marked 'obsolescent' but the alternative ([`clock_getttime()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getttime.html)) is not available on Mac OS X, for instance. – Jonathan Leffler Jul 01 '15 at 16:18
  • @Nawaz This is not the case. The `s` at the beginning of `suseconds_t` stands for *signed*. The unsigned type used to store a number of microseconds in the range 0 to 1000000 is named `useconds_t`. (Notice the lack of the `s` prefix.) – Erwan Legrand Mar 18 '16 at 09:05
  • Languages like e.g. Java force certain capabilities on platforms. As the Java standard says an `int` **must be** 32 bit and signed, a platform that wants to support Java must either have a native signed 32 bit numeric type or must simulate one (which can be slow and even horribly slow). C wants to be a language usable for all platforms that exist, those having a powerful 64 Bit quad core CPU running at 4 GHz and those having just a 8 Bit single core CPU running at 500 kHz. By having a type like `suseconds_t`, the designer of the platform can choose the numeric type that represents it. – Mecki Nov 19 '17 at 01:50
  • Being able to choose the type means you can decide if that type is 32 bit, 64 bit, 20 bit, 24 bit, 48 bit, signed or unsigned, whatever works better for the system, whatever works better for the CPU. There are rules in C, like an `int` being at least 16 bit; but commonly it's 32 bits as that's better, easier, and faster to handle for most platforms. And if a platform wants int to be 24 bit, that's okay as well. C is like a cross platform, high level assembler for writing portable low level code. – Mecki Nov 19 '17 at 01:58
  • To answer your update: You need to understand type promotions in C. Please read this page, it answers all your questions: https://www.safaribooksonline.com/library/view/c-in-a/0596006977/ch04.html Whenever it's possible to convert a type w/o risking using precision, C will do that automatically for you. But when the conversion can lose precision (like converting double to an integral type), you get a warning and you need to explicitly cast. `int a = 5; long b = 8; long c = a + b;` is safe, but `int c = a + b` isn't, you need to force that conversion `int c = (int)(a + b);` at your own risk. – Mecki Nov 19 '17 at 02:12

7 Answers7

7

Because what time_t etc contains are implementation-defined, there is nothing saying that they should contain a number of seconds as an integer, like the comment in your code implies. The reason is that they want these types to be portable between different systems.

In practice, time.h is indeed rather cumbersome, so most of the time programs end up calling system-specific functions instead.

Lundin
  • 195,001
  • 40
  • 254
  • 396
4

As buttiful butterfly correctly points out, the aim of the c++ language and standard library design is to enforce logical correctness at compile time.

What this means is that we aim for a situation where a program that would do the wrong thing under certain obscure circumstances simply will not compile (or will compile, but will warn you).

This means you can fix these logical errors before your software ever even runs in your test harness, let alone in front of your customers.

The result of this is that correctly written c++ code can be mostly proven to be 'correct' before run-time, meaning you spend a great deal less time tracking down obscure bugs that you otherwise would.

The magic of c++ is that is performs this incredible feat while offering a fantastic of efficiency and code optimisation.

note: by 'correct' I mean that it will reliably do what you think you're telling it to do. It's still up to you to write the correct logic!

as to the questions:

So my question is why does C define so many incompatible types instead of simply using primitive types such as int short ...? It's not user-friendly at all.

They are made incompatible in order to deliberately prevent you from converting them to each other. They represent different concepts, in the same way that velocity and distance are different concepts. There is no direct conversion between them.

And how can I do arithmetic operations on these types?

make the intermediate results of your arithmetic something that these types can safely convert to without losing precision. In this case, tv_sec and tv_usec are integral types, so even though they are incompatible with each other, they are both convertible to double individually.

for example:

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;
}
Community
  • 1
  • 1
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • 1
    You haven't really answered the OP's question. You haven't explained how correctness is improved in his situation, and you haven't told him to write code that's safer and/or that doesn't suffer strange warnings. – Steve Summit Jul 01 '15 at 11:13
  • @SteveSummit thanks for highlighting that Steve - i've updated the answer. – Richard Hodges Jul 01 '15 at 11:50
3

To avoid the 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 the datatypes defined in <sys/types.h>, inherently you'll find

typedef long time_t;
...
typedef long suseconds_t;

There are no specific format specifiers for struct timeval but the structure members are of type long. Hope this solves your problem.

Shreevardhan
  • 12,233
  • 3
  • 36
  • 50
  • Expliclit type casting does eliminate the warnings. But how can I know the casting is 100 percent right? Is the casting way elegant? – duleshi Jul 01 '15 at 09:43
  • @duleshi Also read answers of http://stackoverflow.com/questions/471248/what-is-ultimately-a-time-t-typedef-to – Shreevardhan Jul 01 '15 at 09:54
  • Interesting I'm reading that too! But do I have to know the detail of every typedefed types before I use them? And the implementation may change in the future. I don't think that's reasonable. – duleshi Jul 01 '15 at 09:59
  • @duleshi This is primarily opinion based. If you don't want to do that, migrate to C++ and use functions from `std::chrono`. – Shreevardhan Jul 01 '15 at 10:03
  • Using int as an intermediate type is a very bad idea. This code will fail utterly on a 16-bit machine. – Steve Summit Jul 01 '15 at 11:10
  • `struct timeval` is a part of POSIX (see [`gettimeofday()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/gettimeofday.html), for instance). A part verging on obsolescence, but still a part of POSIX. It is not _just_ a GNU C extension. – Jonathan Leffler Jul 01 '15 at 16:14
2

The reason is that the built-in types like int are platform-dependent. So on one computer an int might be good enough to store a time value, while on another you would need a long. To allow people to write programs that run on all platforms, types like time_t were introduces, which are often just alias definitions for some primitive type that is suitable on that particular platform. This in fact takes a little more learning effort in the beginning, but this effort will pay large dividends in the long run.

Makes sense, doesn't it?

[EDIT]: As for the strange warnings: The compiler is warning that the conversion of a time_t and suseconds_t to double might lose some information. That is because both types are integer types with more bits than the mantissa part of double. In your case that would apply only to very large time values, so you can simply ignore these warnings. (But how should the compiler know that a time_t value normally fits into a double? So he emits this warning.) There is in fact not much you can do about that without making the code platform-dependent.

nv3
  • 400
  • 4
  • 9
  • You haven't really answered the OP's question. You haven't explained how correctness is improved in his situation, and you haven't told him to write code that's safer and/or that doesn't suffer strange warnings. – Steve Summit Jul 01 '15 at 11:14
2

[N.B. I have almost completely rewritten this answer since I first posted it.]

The answer to your first question is, C has so many types in an attempt to balance the needs of supporting machines of all different word sizes, with reasonable portability. Things get more complicated due to the desire to also support specialized quantities like "sizes of data structures", "offsets in files", and "times in the real world" reasonably portably, and in spite of the fact that sometimes those specialized quantities end up being determined not by the language spec or the compiler but rather by the underlying operating system.

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

  1. the floating-point type might not be able to represent all the significant digits of the integral type accurately

  2. the floating-point type might not even be able to handle the range of the integral type

In the case of time_t, there is an additional concern:

  1. Type time_t might not be an integer number of seconds that you can meaningfully subtract

(These days, though, the "helpful" compiler warnings in response to these concerns do sometimes seem to border on the nannyish, and I share your concern. It can be hard to understand what obscure situation the compiler is actually worried about, and it can be hard to see how to rewrite the code without warnings, and it can be hard to be sure that any casts you end up having to insert don't end up making the code even less safe.)

If you're not worried about concern #3 (if you're willing to assume that time_t is an integer number of seconds), you can minimize the chances for data loss by doing the subtraction first (and in the 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 should sometype be?

I believe your best bet is to cast to double in each of those places. Both the guaranteed range and precision of type double in C are quite large. So unless you're manipulating time differences greater than 1e50 years (and unless someone has implemented type subsec_t to be a 266-bit type or something), your code should be safe even with the warning-suppressing casts, and you can insert a comment to that effect.

If you want to understand how these concerns can actually manifest in practice, it's easy to demonstrate them. Try this:

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 exactly which of the three concerns your compiler was trying to warn you about. #1 is the likeliest possibility. And you can see how the precision loss disappears if you convert after subtracting, instead of before:

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, this wasn't much of a concern in practice (because IEEE 754 doubles have something like 52 bits of precision). Now that 64-bit types are becoming commonplace, these kinds of warnings are becoming much more widespread. If you're satisfied that type double has enough precision for all your time-subtracting needs, you can use appropriate casts to double to silence the warnings. (And, again, here by "appropriate" we mean "after the subtraction".) If you want to be even safer, you could move to type long double instead. If your compiler supports it, and if it is indeed "longer" than regular double, it can truly reduce the loss-of-precision problem. (Here's the previous example using long double:

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

On my system this one prints 1234.)

But with all of that said, in this case, if you want to really make your life easier -- and, incidentally, address concern #3 at the same time -- you could and arguably should use a standard function for computing the differences. The standard function for subtracting two time_t values is difftime. (It's difftime's job to worry about all of these things, including the possibility that time_t doesn't represent seconds directly.) So you could write

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

although of course there's still the problem of the subseconds.

The very best solution would be a prewritten library function to subtract two timeval's, and you might want to spend some time looking for one of those.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • Thank you for your really understanding my question. Most of the others keeps talking about type safety, but in fact I'm not really that foolish not to understand type safety. However, I'm still not satisfied with the C philosophy. 1)an API which requires explicit casting is ugly; 2)to do the casting, I have to know the detail of the involved types, which is surely not a good design. Why must the user understand the detail of your struct? I would prefer an OOP way `class timer{public: void start(); void end(); double get_diff();};` – duleshi Jul 01 '15 at 12:45
  • @duleshi: You're quite welcome. (I figured you knew about type safety.) In regards to your follow-on question about OOPiness, obviously C isn't an OOP language, but your question and our analysis of it here has proved to my satisfaction that we absolutely need a quasi-standard `timevalsubtract` function, if there isn't one already. (Perhaps I'll try to lobby the GNU folks to add this.) – Steve Summit Jul 01 '15 at 14:33
  • I did a quick search for any standard timeval manipulation functions, and so far all I've found is [this](https://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html) (which at the time of writing says that GNU doesn't have any). – Steve Summit Jul 01 '15 at 15:12
  • 1
    Note that `struct timeval` with microsecond resolution is effectively deprecated by POSIX and `struct timespec` should be used in preference (with nanosecond resolution). This means that functions that do arithmetic on `struct timeval` will have less use than equivalents that work on `struct timespec`. That said, BSD provides some macros to work on `struct timeval` (`timeradd`, `timersub`, `timerclear`, `timerisset`, `timercmp` — operations on timevals) in `/usr/include/sys/time.h`. It would be reasonably sensible to emulate those in any alternative. – Jonathan Leffler Jul 01 '15 at 16:13
  • @JonathanLeffler: Thanks for the reminder about the BSD `timeval` macros. To your other point, though, it seems that not everyone has gotten the memo that `struct timeval` is "effectively deprecated", as we learn in [this other question](http://stackoverflow.com/questions/31217321/portable-way-to-get-time). – Steve Summit Jul 06 '15 at 18:06
0

The reason is called type safety. Not all types of expressions make sense in a working program. And type safety means that the compiler will refuse to permit unsafe, invalid, or inappropriate operations.

In the long run, a compiler that rejects such bad code saves the programmer effort, since it takes more time for a programmer to detect a problem without help than it does for a compiler.

C and C++ have a number of type safety features. Other programming languages have more, others less. Which simply means that some styles of programming are more effective in different programming languages.

Warnings are a little less prescriptive form of type safety - they cause the compilers to warn about suspicious things, rather than reject them entirely.

Peter
  • 35,646
  • 4
  • 32
  • 74
  • You haven't really answered the OP's question. You haven't explained how correctness is improved in his situation, and you haven't told him to write code that's safer and/or that doesn't suffer strange warnings. – Steve Summit Jul 01 '15 at 11:14
  • The OP didn't ASK if correctness is improved. He asked why there are multiple types for which the compiler complains about invalid operations between them. "Type safety" is the short answer to that. – Peter Jul 01 '15 at 11:21
  • I read the question only after he'd added his update. – Steve Summit Jul 01 '15 at 11:37
0

While these type are generally implemented with standard integral types, you can't assume this for portability. The standard library offers some convenient functions for conversions, etc. For example, if you want to compute the delay in between times you can use difftime which returns a double.

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69