Archive | String RSS for this section

K&R Challenge 3 and 4: Functional Temperature Conversion

The other day, I implemented the C solution to exercises 3 and 4 in The C Programming Language, today I’ll be implementing the Haskell solution. As a reminder, the requirements are:

Modify the temperature conversion program to print a heading above the table

… and …

Write a program to print the corresponding Celsius to Fahrenheit table.

I could take the easy way out, and implement the Haskell solution almost identically to the C solution, replacing the for loop with a call to map, but that’s neither interesting to do, nor is it interesting to read about. I’ll be taking this problem to the next level.

Requirements

For my temperature program, I’d like it to be able to convert between any arbitrary temperature unit. For the purposes of this post, I will be implementing Celsius, Fahrenheit, Kelvin (equivalent to Celsius, except 0 degrees is absolute zero, not the freezing point of water), and Rankine (the Fahrenheit version of Kelvin).

That said, nothing about my solution should rely on the fact that these four temperature scales are being implemented. A programmer should be able to implement a new temperature unit with minimal changes to the program. Ideally, just by implementing a new type.

Additionally, given a bunch of conversions, the program should be able to output a pretty table showing any number of temperature types and indicating what converts to what.

First, Conversion

This problem is clearly broken up into two sub-problems: conversion, and the table. First, we need to handle conversion. This being Haskell, the right answer is likely to start by defining some types. Let’s create types for our units:

newtype Celsius = Celsius Double deriving (Show) newtype Fahrenheit = Fahrenheit Double deriving (Show) newtype Kelvin = Kelvin Double deriving (Show) newtype Rankine = Rankine Double deriving (Show)

I’ve chosen to make a type for each unit, and since they only contain one constructor with one field, I use a newtype instead of a data. Now, how to convert these? A straightforward solution to this would be to define functions that convert each type to each other type. Functions that look like:

celsiusToFahrenheit :: Celsius -> Fahrenheit celsiusToKelvin :: Celsius -> Kelvin ... rankineToKelvin :: Rankine -> Kelvin rankineToCelsius :: Rankine -> Celsius

A diagram for these conversions looks like this:

many-to-many

That’s a lot of conversions! One might argue that it’s manageable, but it certainly doesn’t meet requirement #1 that implementing a new unit would require minimal work; to implement a new conversion, you’d need to define many conversion functions as well! There must be a better way.

Let’s think back to chemistry class. You’re tasked with converting litres to hours or somesuch. Did you do that in one operation? No, you used a bunch of intermediate conversion to get to what you needed. If you know that X litres are Y dollars, and Z dollars is one 1 hour, then you know how many litres are in 1 hour! These are called conversion factors.

Luckily for us, our conversions are much simpler. For any temperature unit, if we can convert it to and from celsius, then we can convert it to and from any other unit! Let’s define a typeclass for Temperature:

class Temperature a where toCelsius :: a -> Celsius fromCelsius :: Celsius -> a value :: a -> Double scaleName :: a -> String convert :: (Temperature b) => a -> b convert f = fromCelsius $ toCelsius f

Our Temperature typeclass has five functions: functions to convert to and from celsius, a function to get the value from a unit, a function to get the name of a unit, and a final function convert. This final function has a default implementation that converts a unit to celsius, then from celsius. Using the type inferrer, this will convert any unit to any other unit!

convert Rankine 811 :: Kelvin convert Celsius 123 :: Fahrenheit convert Kelvin 10000 :: RelativeToHeck

Now to implement a new temperature, you only need to implement four functions, as convert is a sane solution for all cases. This arrangement gives us a conversion diagram that looks like:

many-to-one

Much better. Let’s go through our Temperature implementations for our types:

instance Temperature Celsius where toCelsius c = c fromCelsius c = c value (Celsius c) = c scaleName _ = "Celsius"

Of course Celsius itself has to implement Temperature. It’s implementation is trivial though; no work needs to be done.

instance Temperature Fahrenheit where toCelsius (Fahrenheit f) = Celsius ((5 / 9) * (f - 32)) fromCelsius (Celsius c) = Fahrenheit ((9 / 5) * c + 32) value (Fahrenheit f) = f scaleName _ = "Fahrenheit"

Now things are heating up. The conversion functions are identical to the C implementation.

instance Temperature Kelvin where toCelsius (Kelvin k) = Celsius (k - 273.15) fromCelsius (Celsius c) = Kelvin (c + 273.15) value (Kelvin k) = k scaleName _ = "Kelvin"

The Kelvin implementation looks much like the Fahrenheit one.

instance Temperature Rankine where toCelsius (Rankine r) = toCelsius $ Fahrenheit (r - 459.67) fromCelsius c = Rankine $ 459.67 + value (fromCelsius c :: Fahrenheit) value (Rankine r) = r scaleName _ = "Rankine"

The conversion between Fahrenheit and Rankine is much simpler than the conversion between Celsius and Rankine; therefore I will do just that. After converting to and from Fahrenheit, it’s a simple matter of calling toCelsius and fromCelsius.

Bringing The Monads

Now that the easy part is done, we get to create the table. Our table should have as many columns as it needs to display an arbitrary number of conversions. To that end, let’s define a data structure or two:

data ConversionTable = ConversionTable [String] [[TableRowElem]] deriving (Show) data TableRowElem = From Double | To Double | NullConv Double | Empty deriving (Show)

The ConverstionTable, like the name suggests, is our table. The list of strings is the header, and the list of lists of TableRowElem are our conversions. Why not just have a list of Double? We need to have our cells contain information on what they mean.

To that end, I created a TableRowElem type. From is an original value, To is a converted value, NullConv represents the case were we convert from some type to the same type, and Empty is an empty cell. The problem of how to place elements into this data structure still remains however. To solve that, things are going to get a bit monadic. Let’s define some intermediate builder types:

type Conversion a b = (a, b) toConversion :: (Temperature a, Temperature b) => a -> (a, b) toConversion a = (a, convert a)

First we have Conversion, and the corresponding toConversion function. This simply takes a unit, and places it in a tuple with its corresponding conversion. Next, we have the TableBuilder:

type TableBuilder a = WriterT [[TableRowElem]] (State [String]) a

Here we have a WriterT stacked on top of a State monad. The writer transformer contains the list of table rows, and the state monad contains the header. The idea is that as rows are “logged” into the writer, the header is checked to make sure no new units were introduced. To this end, if only two units are introduced, the table will have two columns. If 100 units are used, then the table will have 100 columns.

NOTE: I realize that WriterT and State are not in the standard library. I only promised to limit the usage of libraries for Haskell solutions. This means avoiding the use of things like Parsec or Happstack. Frameworks and libraries that vastly simplify some problem or change the way you approach it. To this end, if I feel a monad transformer or anything along these lines are appropriate to a problem, I will use them. I’ll try to point out when I do though. Besides, I could have just re-implemented these things, but in the interest of not being a bad person and re-inventing the wheel, I’ve decided to use a wheel off the shelf.

So, how do we use this TableBuilder? I’ve defined a function for use with this monad:

insertConv :: (Temperature a, Temperature b) => Conversion a b -> TableBuilder () insertConv (a, b) = do oldHeader <- lift $ get workingHeader <- ensureElem a oldHeader finalHeader <- ensureElem b workingHeader lift $ put finalHeader tell [buildRow a b finalHeader []] where ensureElem a h = return $ case ((scaleName a) `elem` h) of True -> h False -> h ++ [(scaleName a)] buildRow _ _ [] r = r buildRow a b (h:xs) r | (scaleName a) == (scaleName b) && (scaleName a) == h = r ++ [NullConv $ value a] | (scaleName a) == h = buildRow a b xs (r ++ [From $ value a]) | (scaleName b) == h = buildRow a b xs (r ++ [To $ value b]) | otherwise = buildRow a b xs (r ++ [Empty])

Yeah, that one is kind of a doosey. Let me walk you through it. This function takes a Conversion, and returns a TableBuilder.

In the first four lines of the do block, we update the header. We lift the State monad, then get we call ensureElem with the first and second units, then we put the new updated header back into the State monad.

The ensureElem function checks the header list to see if the current unit is a member. If it is, the header list is returned unchanged, if it’s not the unit is appended to the end and the new list is returned. In this way, whenever a conversion is added to the table, the header is updated.

After updating the header, we call tell with the result of buildRow, “writing” the row into the Writer monad. The buildRow function recursively adds TableRowElems to the result list depending on the current heading. In this way, conversions are placed in the appropriate column.

In addition to that function, I’ve defined a function to simplify working with the TableBuilder:

buildTable :: TableBuilder a -> ConversionTable buildTable b = let result = runState (runWriterT b) [] in ConversionTable (snd result) (snd $ fst result)

Working with some of these MTL monads can be confusing for people coming from imperative backgrounds. I’ve been working with Haskell for almost a year now and I still get extremely confused by them. It can take some muddling through haddoc pages to work them out, but the good news is that you mainly just need to define one function that takes a monad (in the form of a do block), and returns a whatever. The buildTable function takes a TableBuilder, and returns a ConversionTable. It handles calls to runState and runWriterT, and then unwraps the resulting tuple and builds the ConversionTable.

This function can be called like this:

buildTable $ do insertConv someConversion insertConv someOtherConversion

… and so on. The only thing to remember is that the final value of a for the do block must be (). Conveniently, insertConv return a value of type TableBuilder (), so if the last call is to this function, then you are good. You can also always end it with return () if you like.

Pretty Printing

Finally, we have the matter of printing a nice pretty table. For that, we need yet another function:

prettyPrint :: ConversionTable -> String prettyPrint (ConversionTable h r) = let widestCol = last $ sort $ map length h columnCount = length h doubleCell = printf ("%-" ++ (show widestCol) ++ ".1f") stringCell = printf ("| %-" ++ (show widestCol) ++ "s |") emptyCell = replicate widestCol ' ' horizontalR = (replicate (((widestCol + 4) * columnCount) + 2) '-') ++ "\n" formatRow row = "|" ++ (concat $ map formatCell row) ++ "|\n" formatCell (From from) = "| " ++ (doubleCell from) ++ " |" formatCell (To to) = "> " ++ (doubleCell to) ++ " |" formatCell Empty = "| " ++ emptyCell ++ " |" formatCell (NullConv nc) = "| " ++ (doubleCell nc) ++ " |" in horizontalR ++ ("|" ++(concat $ map stringCell h) ++ "|\n") ++ horizontalR ++ (concat $ map formatRow (normalizeRowLen (columnCount) r)) ++ horizontalR where normalizeRowLen len rows = map (nRL' len) rows where nRL' len' row | (length row) < len' = nRL' len' (row ++ [Empty]) | otherwise = row

Yeah… Sometimes the littlest things take the most work. You’d think all this plumbing we’ve been doing would be the most complecated bit, but you’d be wrong. Let’s try to make sense of this mess function by function:

widestCol = last $ sort $ map length h

This function determines the widest column based on the header. Typically, this is going to be “Fahrenheit”, but it doesn’t have to be. It should be noted that if a data cell is wider than this, then the pretty printer will mess up. Like most things in life, there is room for improvement here. That said, unless you’re converting the temperature of the core of the sun, you probably won’t have an issue here.

columnCount = length h

Returns the number of columns in the table. Used by the horizontal rule function.

doubleCell = printf ("%-" ++ (show widestCol) ++ ".1f")

Ahh, our old friend printf. It exists in Haskell and works in much the same way as it did in C. The doubleCell function converts a temperature value to a string, left aligns it, pads it by widestCol, and has it show one decimal place.

stringCell = printf ("| %-" ++ (show widestCol) ++ "s |")

Much like with doubleCell, this function pads, and left-aligns a string. This is used by the header.

emptyCell = replicate widestCol ' '

This one is pretty self-explanatory. It prints an empty cell of the appropriate width.

horizontalR = (replicate (((widestCol + 4) * columnCount) + 2) '-') ++ "\n"

This function prints a horizontal rule. This will be a solid line of “-” across the width of the table.

formatRow row = "|" ++ (concat $ map formatCell row) ++ "|\n"

This function formats a table data row. It maps formatCell over the list of cells, flattens it, then adds a pretty border around it.

formatCell (From from) = "| " ++ (doubleCell from) ++ " |" formatCell (To to) = "> " ++ (doubleCell to) ++ " |" formatCell Empty = "| " ++ emptyCell ++ " |" formatCell (NullConv nc) = "| " ++ (doubleCell nc) ++ " |"

In this function, much of the work is done. It formats the cell using doubleCell or emptyCell, the applies a border to the cell. It denotes a cell containing a To by adding a > on the left.

Now that we’ve covered the let-bound functions, let’s talk about the actual function body:

horizontalR concat $ map stringCell h) ++ "|\n") horizontalR concat $ map formatRow (normalizeRowLen (columnCount) r)) horizontalR

This bit is prett straightforward. First, it prints a horizontal line. Second, it maps stringCell over the header list, flattens it, and gives it a border. Third it prints another horizontal line. Fourth is maps formatRow over the normalized row list, then flattens it. Finally, one last horizontal line. After this is all said and done, it concats it all together.

You may be wondering about that normalizeRowLen function. If you were paying particularly close attention to the insertConv function, you may have noticed an issue. Let’s walk through it in ghci:

*Main> let fc = toConversion (Fahrenheit 100) :: (Fahrenheit, Celsius) *Main> buildTable $ do insertConv fc ConversionTable ["Fahrenheit","Celsius"] [[From 100.0,To 37.77777777777778]]

We add one conversion, we get two columns. Everything seems to be in order here, but let’s add another conversion and see what happens:

*Main> let fc = toConversion (Fahrenheit 100) :: (Fahrenheit, Celsius) *Main> let cr = toConversion (Celsius 100) :: (Celsius, Rankine) *Main> buildTable $ do {insertConv fc; insertConv cr;} ConversionTable ["Fahrenheit","Celsius","Rankine"] [[From 100.0,To 37.77777777777778],[Empty,From 100.0,To 671.6700000000001]]

See the problem? Let’s add some newlines to make it clearer:

ConversionTable ["Fahrenheit","Celsius","Rankine"] [[From 100.0,To 37.77777777777778], [Empty,From 100.0,To 671.6700000000001]]

As we add more columns, the rows with less columns are never updated to have the new column count. Logically, this is fine, since the extra entries would just be Empty anyways, but our pretty printer would print this table like so:

-------------------------------------------- || Fahrenheit || Celsius || Rankine || -------------------------------------------- || 100.0 |> 37.8 || || || 100.0 |> 671.7 || --------------------------------------------

As you add more and more columns, the problem gets worse and worse. Enter our normalizeRowLen function:

normalizeRowLen len rows = map (nRL' len) rows where nRL' len' row | (length row) < len' = nRL' len' (row ++ [Empty]) | otherwise = row

This is another fairly straightforward function. If the row has the same number of columns as the header, it is returned unchanged. If it doesn’t, Empty is added to the end until it does.

With that, our program is complete. Let’s try it out:

main = do k <- return (toConversion $ Kelvin 100 :: (Kelvin, Rankine)) f <- return (toConversion $ Fahrenheit 451 :: (Fahrenheit, Kelvin)) r <- return (toConversion $ Rankine 234 :: (Rankine, Celsius)) c <- return (toConversion $ Celsius 9 :: (Celsius, Fahrenheit)) nc <- return (toConversion $ Rankine 123 :: (Rankine, Rankine)) putStrLn $ prettyPrint $ buildTable $ do insertConv k insertConv f insertConv r insertConv c insertConv nc

In our main, we create a bunch of conversions. Then we prettyPrint them and putStrLn the result. The following will be printed to the console:

---------------------------------------------------------- || Kelvin || Rankine || Fahrenheit || Celsius || ---------------------------------------------------------- || 100.0 |> 180.0 || || || |> 505.9 || || 451.0 || || || || 234.0 || |> -143.2 || || || |> 48.2 || 9.0 || || || 123.0 || || || ----------------------------------------------------------

Any type that implements Temperature can be put into a table this way. To add a new unit to the program, it’s as easy as implementing four one-line functions!

Imagine: A World Without Strcmp

Recently, I’ve been working on implementing the config file for DMP Photo Booth, and luckily for me, GLib has the answer for this: GKeyFile. GKeyFile is GLib’s implementation of those flat config files you tend to see in Linux applications:

#Some comment! [section] value=words some_bool=true

…like that. I happily set to work, satisfied that today was not the day that I’d have to begin dealing with FILE pointers. Unfortunately, though I don’t have to deal with straight up file IO, I do still have to deal with functions that will fail if you so much as look at them funny. All the functions of GKeyFile take a group and key name string to look up, and if you pass in a bad string, things go off the rails. At first I had planned to use #defined strings, but halfway through implementing input validation in my first access function, I thought that there must be a better way. Strings are “difficult” to compare against known values, and it all seemed like so much work!

Next, I thought of using magic integers, but I’d have to come up with some way to resolve them to a string. Since this was all starting to sound like the beginnings of implementing a hash table, I turned to GLib for its implementation. This also seemed like more effort than it was worth. As I was poking around the docs, I found my answer!

Introducing Quark!

I’m talking about the GQuark. A GQuark functions in a similar manner to a hash table (in fact, if you dig through the source, you’ll see that GQuark uses a hash table.) GQuarks are a two-way association between a non-zero guint32 and a gchar * (A GQuark value of 0 resolves to NULL.) Basically, once you register a string as a quark, you can pass the GQuark object around and use it to obtain the string. You can also attempt to resolve a string to get its integer value if it has been registered. The best part about a GQuark is you can directly compare the GQuark object using operator==, avoiding a call to strcmp(), reducing boilerplate code.

Notable Functions

GQuarks do not have a lot of moving parts. Below are some important functions for using GQuark:

G_DEFINE_QUARK(some_string, function_name)

This macro defines a function named function_name_quark that returns a quark for “some_string”. Basically, this is your replacement for:

#define MAGIC_STRING_NAME "magic_string_literal"

Where you might use the MAGIC_STRING_NAME before, you can now just call your newly defined quark function! Notice up above that in the macro parameter, some_string is not in quotation marks. This is important; the function defined by the macro will strigify the first parameter. For example, if you enter this:

G_DEFINE_QUARK(The quick brown fox jumped over the lazy dog's back\n, dog_fox)

You’ll get a function:

GQuark dog_fox_quark()

…that returns a GQuark for the string:

"The quick brown fox jumped over the lazy dog's back\n"

The second thing to note about this macro is that it declares AND implements the function. In other words, it expands to something like this:

GQuark name_quark() { ...code... }

As you can see, it would be an error to place a semicolon behind the macro.

All of that may seem like a lot to remember, but if you want to define several quarks, it will save you hours of typing.

g_quark_to/from_string and friends

These functions are your GQuark manipulation functions. They will let you convert strings and GQuarks to each other. For instance, if you enter:

g_printf("%s\n", g_quark_to_string(some_quark));

You will print the string contained in some_quark. Pretty straight-forward.

Thread Safety

You may be thinking: “gee, this sounds like it uses a whole lot of globals! Is this thread safe?” I know I was, so I did a little investigation. Looking through the source for GQuark, all the functions that access global variables are protected by a mutex; therefore you may feel free to use this from multiple threads.

GString: A Revelation

As anybody who has ever developed in a modern object-oriented or scripting language and then learned C can tell you, working with strings in C sucks. It sucks really hard. Let me list some of the ways that C strings are terrible:

  • Strings are bare char arrays in C; they require manual memory management
  • As pointers, C strings are allocated on the heap. Unless they are string literals, in which case they are magical immutable things. Don’t you dare attempt to free() a string literal
  • There is no way to test if a char array is a heap-allocated pointer or a string literal
  • C strings have to be null terminated, or nothing works
  • There is no reliable way to tell the length of a C string; strlen() will give incorrect results if the string isn’t null terminated.
  • Copying a C string requires you to have allocated a char array greater than or equal to the size of the string to be copied. However, strlen() returns the size required, -1
  • String concatenation requires the management of 2 pointers
  • Most C string functions have “n” versions, that you should use instead of the base versions if you don’t want your program to be hacked

I’m sure there are others, but it’s late. The point here is that dealing with C strings is kind of a drag. Unfortunately for us, this comes with the territory; losing the baggage of object orientation also requires us to lose its conveniences. Or so I thought…

GString To The Rescue

While looking through the reference manual for GLib, I noticed a section: Strings. You read that right, GStrings are a thing! GLib provides a string class that does all the usual sorts of things you’d expect of a proper string object. Let’s talk about a few of them.

g_string_new

This is your string constructor. The documentation says that it “Creates a new GString, initialized with the given string.” You’ll notice that it doesn’t say who is responsible for the original char * when the string instantiation is complete. In search of the truth, I turned to Google.

Shockingly, Google proves unhelpful. After poking around, I decide to just look in the source code. Under the covers, g_string_new calls out to g_string_append_len, which in turn calls out to g_string_insert_len. G_string_insert_len does some pointer jimmying, then finally calls memcpy to copy the contents of the char array into the string. The verdict: the GString does not take ownership of your char array; make sure you free it (if it’s not a string literal, of course) when you’re done with it.

g_string_free

This is your string destructor. This function takes two arguments: the string to free, and a gboolean. The gboolean is important; if you set it TRUE, it will free the underlying char array inside the GString. If you set it FALSE, then you are on the hook for its memory management. This is useful you want to return the char array from a function and you don’t need the GString object any more.

g_string_append and friends

GString provides a suite of string concatenation functions. You can append at the front or the end of a GString. The best part is that you don’t even have to worry about resizing the array; GString does all that for you!

g_string_printf and friends

GString provides several printf functions that function pretty much how you’d expect: they facilitate writing formatted strings into a GString using the semantics of printf. This allows you to convert non-string types to strings, and format your data. For us Java types, this is your FooType.toString() method replacement.

Public Fields

GString has three public fields: str, len, and allocated_len. Str is a pointer to the underlying C string. If you need to access the actual data of a GString, just call the_string->str. Len is the length of the string. This would be equivilent to a call to strlen(), but it is not dependent on a terminating null character. In fact, a GString can have null characters throughout without issue! Finally, allocated_len is the actual allocated size of str.

Consequences Were Never The Same

GString solves many of the issues of C Strings:

  • GStrings encapsulate your C Strings; no longer are you subject to the tyranny of malloc
  • If a function returns a GString, you need not worry if it is a heap allocated pointer or a string literal
  • GStrings don’t need to be null terminated
  • Testing the length of a GString is a simple matter of checking its len field
  • Copying a GString is a simple call to g_string_new(to_copy->str)
  • String concatenation is a simple method call requiring no math

In a world with GStrings, string management in C became just a bit more tolerable. Object-oriented refugees worldwide give their heartfelt thanks to the brave men and women who gave us the GString.

Pining for toString()

One of the things that I’ve had difficulty with coming from Java to C is dealing with strings. In Java, string handling is just so easy, they’re nice little objects that keep to themselves and don’t bother anybody. Everybody loves them, and wants to be them. Everybody has a method called toString() that lets them turn into one if you’ll just let them. Want to stick an EnterpriseBaseBeanFactoryFactory into a JLabel? EnterpriseBaseBeanFactoryFactory.toString() and you’re set. Wouldn’t it be nice if we had toString() in C?

About That

We sort of do. Like all things in C, it requires a bit of work, but we can replicate the effect. The function you are looking for is snprintf(), which is in stdio.h. snprintf() is a variadic function that works similarly to printf(). However, instead of printing to stdout, it places the string into a passed-in char *. snprintf() takes at least 3 arguments: a destination char *, the maximum number of bytes to output, and a format string. Following the format string, a variable number of extra arguments corresponding to the format string are passed in, similarly to printf().

Snprintf() does not do any pointer allocation or deallocation, so you should create your char * ahead of time. It can be a stack allocated char array too if you only need a temporary variable. The second argument should be less than or equal to the size of the char *. This size argument prevents snprintf() from writing off the end of your allocated memory.

For Example

Take the following code:

int value = 1000; char to_string[5]; int return_value = snprintf(to_string, 5, "%d", value); printf("Return value: %d, The String: %s", return_value, to_string);

This code creates a char array sized 5, and converts the number 1000. This code produces the following output:

Return value: 4, The String: 1000

…but 1000, is only 4 digits, right? Let’s see what happens if we reduce to_string[]’s size to 4, and the size argument to 4:

int value = 1000; char to_string[4]; int return_value = snprintf(to_string, 4, "%d", value); printf("Return value: %d, The String: %s", return_value, to_string);

…produces an output of:

Return value: 4, The String: 100

Snprintf() wrote “100” into the string and returned 4. What happened? When snprintf() tries to write a number of bytes greater than the limit, it returns the number of bytes that it would have written if the size was large enough. You can use this to test for failures and to determine how big your string needs to be. However, this does not explain why the string was truncated. Snprintf() returned 4, and our limit and array size were both 4, it should have worked, right?

The problem is that we are forgetting the terminating . Snprintf() appends to the end of strings it creates, and size needs to be left over for this null. However, the return value does not account for the terminating null.

What About Sprintf()?

In stdio.h, there is another function that appears to do the same thing: sprintf(). Sprintf() doesn’t take a size_t like snprintf() does. Surely this means that sprintf() does some sort of automatic checking right? No, it doesn’t. Sprintf() will happily write off the end of your passed-in char * and cause all sorts of problems. As we all know, this will result in undefined behavior. It might seem to work. It might segfault. It might even cause lions to emerge from your fridge and eat you. One thing I’m sure we can all agree on is: lions are terrifying. Do yourself a favor, and just use snprintf().

Portability Concerns

Like many things in C, word on the street is that snprintf() behaves differently across different platforms. This blog post was written using GCC and Linux. I can’t promise that snprintf() works exactly the same in Visual Studio, or on some random mainframe or whatever. As always, try it before you buy it.

%d bloggers like this: