Hacker News new | past | comments | ask | show | jobs | submit login
Resume in C (gist.github.com)
291 points by brudgers on Mar 17, 2015 | hide | past | web | favorite | 112 comments



Wow, I didn't know this was valid C code, it's pretty convenient.

  school_t uiuc = {
      .school   = "University of Illinois at Urbana-Champaign",
      .location = "Urbana, IL",
      .program  = "BS Computer Science",
      .started  = 1251158400,
      .left     = 1336608000,
      .accomplishments = {
          "Minor in International Studies in Engineering, Japan",
          "Focused on systems software courses",
          NULL
      }
  };


I highly recommend "21st Century C" (http://shop.oreilly.com/product/0636920025108.do) as a tour of modern C programming, including use of C99 features. It goes over C tooling (profiling, debugging, testing, cross-platform deployment), and explores C99.


There's a newer edition available which has been published recently:

http://shop.oreilly.com/product/0636920033677.do


Following your recommendation, to people interested in learning about Modern C++, Stroustrup's Tour of C++ is a recommended read.

http://www.stroustrup.com/Tour.html


These are called desginated initializers, they're part of C99.[0] This is a good read which discusses them and also devles into compound literals.[1]

[0] https://gcc.gnu.org/onlinedocs/gcc/Designated-Inits.html [1] https://nickdesaulniers.github.io/blog/2013/07/25/designated...


It's a C99 feature (I think?) -- designated literals. They're absolutely wonderful.


Although GP didn't remark on it, TFA uses an other cool feature which is actually C11: anonymous unions. Although not as wonderful as designated literals, they're really quite convenient.


It's supported by GCC as an extension to C90 too.


what does that even mean? Isn't C99 an extension of C90/ANSI? Doesn't GCC support the whole of C99 as an extension to ANSI?


It means that GCC will allow/support this even if you set it to std=c89 or std=c90.


Won't that only be supported in gnu89 or gnu90 mode?


No, it will be supported using -std=c89 or -std=90, unless you also specify -pedantic.


Yes, I realized after posting that -pedantic is required for strict standards compliance.


The GNU modes just enable additional features which conflict with plain C. Any feature which can coexist with plain C will be enabled in the regular C modes too. For example, __typeof__ is enabled everywhere, because the compiler is allowed to do just about anything it wants with a __ prefix, but plain typeof is only enabled in the GNU modes, since it could cause valid C programs using "typeof" for their own purposes to fail to compile.


C99 is not a strict extension, but as stated in its foreword, "this second edition [C99] cancels and replaces the first edition [C90]".

For instance, implicit function declarations are allowed in C90 but not in C99.


I so, so wish C++ would adopt this feature.


Designated initializers are one of a few indispensable features that make "C++ is a better C" a complete non-starter for me.


They're used heavily in Linux device drivers, amongst other things - here's just one example:

https://github.com/torvalds/linux/blob/master/drivers/video/...


i learnt about them from this post and the library it refers to: http://spin.atomicobject.com/2014/10/08/c99-api-designated-i...

good read, both the post and the socket99 code.


My gcc required me to put { } around any fields of unnamed unions.

[gcc version 4.4.7 20120313 (Red Hat 4.4.7-11) (GCC)]


gcc 4.4 is getting kind of long in the tooth.


When I was in university, I did something similar in Python. I printed it off in color and brought it to a career fair. One guy loved it excitedly. But I didn't hear back from him :(. And that's the story of how I cam to work in Java ;)


This is why when you meet someone at a networking event:

0) Have brought a pen or cell phone.

1) Always get their business card or contact info.

2) When you get someone's business card, always write down where you met them and what you want to ask or show them. Otherwise, you end up with a pile of cards and no memory of who they were from.

2.1) If you actually had them type their email into your phone, sketch out the email, but don't send it.

3) follow up within 3 days or you'll forget.


    --- a/resume.c
    +++ b/resume.c
    @@ -118,7 +118,7 @@ void print_job(job_t * job) {
            }
     }
    
    -int main(int argc, char * argv) {
    +int main(int argc, char ** argv) {
    
            int i = 0;
            while (jobs[i]) {
    @@ -127,4 +127,4 @@ int main(int argc, char * argv) {
            }
    
            return 0;
    -}
    \ No newline at end of file
    +}


For those who don't write C, could you explain what is significant about this change? I'm guessing the earlier version was some kind of subtle bug?


You input arguments into your program using argc and argv. argc is number of arguments and argv is the list of arguments.

There is no string in c so you use char pointers instead. But in previous code he used char, which is usually defines a single string, but main actually inputs an array of string so it should have been char* (pointer to pointer of char, or pointer to 'string')

Basically he did something like this in Java

public static void main(String args)

instead of

public static void main(String args[])


  char * argv
is a pointer to a string (or a single character).

  char ** argv
is a pointer to an array of pointers to strings. The latter is used since it allows for multiple arguments in (the number being `int argc`).

This picture shows the second example in terms of pointers:

http://www.londonquilters.org.uk/ctext/pic511.gif


Wouldn't second simply segfault when you try to access it?


Nope, neither will segfault on the initial access.

  char ** argv
is a null terminated array in C according to the C standard. (EDIT: see clarification below) C doesn't care about types at all and you can abuse this by casting pointers to different (sometimes incompatible types). The second argument to main (argv) it set up by the initialization code and has the same space as a pointer regardless if it's a string, array of strings, struct, integer etc...

The existing structure will simply be interpreted as that new type like so:

                String1       String2    String3     String4
  char ** a2: |0x41414141 |0x42424242 |0x43434343 |0x00000000 |
              +-----------+-----------+-----------+-----------+
  char * a1 : |41|41|41|41|42|42|42|42|43|43|43|43|00|00|00|00|
  
  printf("%s\n", a1);
  > AAAABBBBCCCC
That being said, some compilers (clang) will emit an error on the improper type, like so:

  test.c:3:5: error: second parameter of 'main' (argument array) must be of type
        'char **'
  int main(int argc, char * argv)
      ^
  1 error generated.


We are talking about C and not a specific compiler, so there are a lot of mistakes in your comment.

char * * is a pointer to a pointer to a char and nothing else, it might be a NULL terminated array or not at all.

C cares about types. Casting a pointer to an incompatible type or reinterpreting through an incompatible pointer is not defined in C( undefined behavior ).

Pointers of different types are allowed to have different sizes. sizeof( char* ) == sizeof( char* * ) is not guaranteed in C.

Also any program whose main is not int main( void ) or int main( int , char* * ) will result in undefined behavior.

All of this is in the latest standard.

Both, segfaulting or running completely normally can be a result of undefined behavior. That is why we really like to avoid it in C. Thus your advice is really not good for a modern C.


In the case of argv though on main it is defined to be null terminated on argv[argc] according to the standard. Assuming the pointers are the same size though what I said will work but is bad practice.

> sizeof( char* ) == sizeof( char* * )

True, I forgot that different architectures/compilers can produce different pointer sizes. Thanks!


char* * argv is NULL terminated, my mistake I though you were referring to that type in general.


Code review:

* The struct members and variables pointing at string literals should be const.

* People do break this all the time but typedefs ending in _t are reserved by POSIX.

* Your C99 style struct initializers are valid but it's notable that one popular compiler, MSVC, will not like them.

* If we are being idiomatic I would suggest pointer arithmetic to iterate instead of using a counter i.


I think we have bigger problems to address before we get to these minor contrivances.

This resume is not web scale enough. We need to wrap the resume in node.js so it can be non-blocking for big data. It should be written in an esoteric JS variant and transpiled to ES5 because everyone knows ES5 is unreadable. Oh and the storage of his education and job history should really be stored in a NoSQL database like MongoDB that way he can shard asynchronously. When he presents the resume in his interviews it should be wrapped in a webkit container.


Style review:

* Avoid using NULL in your resume. It has a negative connotation, and makes your resume look bad.

* Make use of as many existing libraries as possible, to show that you're not the kind of programmer that wants to invent the wheel on every occasion.

* Try to obfuscate your resume a little (but not too much!) As it is written here, your reader can easily guess what the program will do, and he/she will not even want to run it anymore.

* #include a portfolio of your work inside your resume.


You had me going for a moment there. I was ready to respond to your comment about NULL before I realised you were joking.


> * Your C99 style struct initializers are valid but it's notable that one popular compiler, MSVC, will not like them.

The code compiled fine with MSVC 2013 (simply 'cl resume.c'). No warnings at all.


I think they they implemented it very recently. 2012 will fail.


> If we are being idiomatic I would suggest pointer arithmetic to iterate instead of using a counter i.

Well, the CVE guys need to keep their job.


He's already iterating by using null termination instead of based on array bounds. I am suggesting he do it in the more common way.

On another topic, I have noticed you post this sort of trollish c bashing on almost every hn thread about c. It's fine that you prefer something else, but maybe time to give it a rest?


Someone needs to spread the gospel of safe systems programming to young minds not exposed to the existing alternatives, before C grew out of UNIX.

If C didn't need this kind of attention to safety, Clang, GCC and others wouldn't have all those nice analyzers built-in, not MISRA would be required.

Besides, looking for my post history and fellow comments, I am not alone in this.


You're not alone in that a lot of people here are not shy about expressing that they feel more productive working in languages with GC and bounds checking. That's fine.

I happen to think that a good understanding of C has made me a much better developer. I don't think your opinions, however strong, genuine, and coming from a real place, invalidate that.

What I don't appreciate are the attitudes that C is uninteresting, that nobody is productive in C, etc. Or that it is phrased as a moral duty to avoid C. I feel like it's too common to overstate the dangers. Safe C is still possible.


I think Peter Van Der Linden would agree. He states in Deep C Programming that the separation of the compiler from the linter, and I assume by extension semantic analysis tools as well, was a mistake in retrospect. It's good to see that compilers like Clang are bringing them back together in a way.

One does have to be more careful when writing C but I don't "blame" C for unsafe code. It's the kind of language that assumes the programmer know what they're doing. In this regard it is very unforgiving.


Yes lint does exist since 1979 and Ritchie remarks in his notes that it was already a need back then.

The problem got worse when C dialects started to appear outside UNIX without Lint. Or the compiler vendors selling it separately of the compiler.

The problem with languages that trust the developer is that they are based on a set of false premises, namely:

- the team is composed from top skill developers

- the team never changes

- everyone knows the whole code base

Which are all false in the majority of corporations.


I don't understand your false premises. I've never had C marketed to me as a language that promised safe, perfect code. It does what it says on the tin.

However I think you have some false premises: the Linux kernel is huge, has a fairly high turn-over in contributors, and is written in C. Hardly anyone "knows" the entire code base. I've yet to meet anyone who can even name all of the compile switches... some 1000+ of them. Yet it's hard to argue that it's not useful or impossible to contribute to. I'm not a genius and if I can figure it out I'm sure I can teach others.

Yes, you can write bad code in C. I don't think you can blame C for human error. There are sins of commission and those of omission and the ANSI C specification documents them both quite well. Designing a language that protected an ignorant and uneducated programmer from making mistakes wasn't one of their goals I'm afraid.


There is a big difference between writing C code and C code that is safe from exploits, even Linux isn't immune to it as the daily CVE updates show.

http://www.cvedetails.com/vulnerability-list/vendor_id-33/pr...


Correct and I don't find this surprising.

While the separation of lint from the compiler might have been a mistake made as an early performance trade-off I don't really see how that was a false premise of the philosophy of trusting the programmer. Given the history of trade-offs made in the name of performance it doesn't seem like correctness and safety were big concerns.

Maybe they are now and that's why I find the Mirage project interesting... but C still has its uses and I don't blame C for human error. The specification isn't terribly difficult to digest and the tooling is rather good these days.


> * If we are being idiomatic I would suggest pointer arithmetic to iterate instead of using a counter i.

How exactly is one more idiomatic than the other?


It's subjective. It feels more c like and natural to me to write while (foo->bar) and not while (foo[i].bar). The latter feels like the way you would do it somewhere else, grafted onto C. But it's not wrong, I don't have deep qualms with it, and that's why I put it last on my list.


It's not subjective. Intel's guidelines for writing vectorizable code:

Prefer array notation to the use of pointers. C programs in particular impose very few restrictions on the use of pointers; aliased pointers may lead to unexpected dependencies. Without help, the compiler often cannot tell whether it is safe to vectorize code containing pointers.

Source: https://software.intel.com/sites/default/files/8c/a9/Compile... (Section 5.1)


yep - signal your intentions as specifically to the compiler as possible, so that it can generate as optimized output as possible.

Array access is more specific and intentional than pointer arithmetic.


What are the odds this turns out to be practical advice for this example? Are you going to write all your loops like that because someone at Intel told you it was a good idea, or are you going to measure it when it's seen to be a problem?


What are the odds this turns out to be practical advice for this example?

High. E.g. gcc 4.8.2 vectorizes this on my machine (-march=native -mtune=native -O3 -ffast-math -ftree-vectorizer-verbose=1):

  for (size_t i = 0; i < n; ++i) {
    sum += a[i];
    sum += b[i];
  }
(a and b have the same length)

But doesn't vectorize:

  for (double *p = a; p < a + n; ++p) {
    sum += *p;
    sum += *(b + (p-a));
  }
(Yes, you can make an extra pointer for b, but just for the sake of the example.)

tl;dr: your compiler writer probably knows better than you.


The poster did not sum values. They used the loop to call into printf. I'm guessing they didn't also use those particular compiler options... What you have here sounds like a lot of tuning that does not apply equally for all scenarios.


I would have to agree. IMO it lends itself to cleaner code if you're working with NULL terminated lists, especially if you use a `for` when doing the iteration instead of a `while`:

    const char **a;

    for (a = job->accomplishments; *a; a++)
        printf(" - %s\n", *a);
And

    job_t *j;
    for (j = jobs; *j; j++)
        print_job(j);
The person who wrote this code seems to dislike `for`s for some reason it seems, these are pretty obvious places to use one IMO, using an index `i` or not. Doing it with a `while` separates out the initialization, condition, and increment in his code, and 'continue' won't work like you normally want it too.


> People do break this all the time but typedefs ending in _t are reserved by POSIX.

hahaha, shame on me, I always use _t to abbreviate types or make custom ones, ex. uint_t, fraction_t, bitfield32_t, etc...

bitfield32_t: https://gist.github.com/AldoMX/526e84da13b00ac0c75e


This is a hit or miss. Say they are looking for a C coder. A bad recruiter (most of them) would just discard this as he/she wouldn't even recognize it as C code, just some junk not fitting into his/her template. A good recruiter though, or if a recruiter wasn't used - they would see this as smart and creative and definitely get you an interview.

I've seen companies shutting down/relocated because of bad recruiters filtering good programmers like this.


Personally, I wouldn't want to work for a company that uses bad recruiters (and thus probably hires bad programmers), so something like this would be doing its job.

You don't have to go this far. My CV is a HTML page, and I just refuse to convert it to Word. It hinders the recruiters who just like to buzzword search their database.


When I saw the title, I thought it was going to be about pausing and resuming a thread of execution or some stack.

Could an editor put the slanty thing over the e in the title?


FYI the "slanty thing" is called an acute accent.


Just curious, but why do you use

  initialize
  while (test) {
    ...
    increment
  }
instead of

  for (initialize; test; increment) {
    ...
  }


It's cheeky, but then again it is a project you can show someone. Graphic designers get to make fancy schmancy resumes to impress their potential employers, so why not programmer? Better make sure there are no bugs in it though.


Ah nothing like handing your resume over to a prospective employer and having it SEGFAULT


If they'll run unknown code without checking it first that's something you'd want to know about.


Reminds me of Major Hayden's man page résumé[0]

[0]http://majorhayden.com


Nice. Here's one I always remember (written in Haskell) - https://ocharles.org.uk/

OCharles originally did it in Perl/Moose and with a module of mine (Acme::URL) which is how I stumbled across the CV in the first place - https://web.archive.org/web/20120119150530/http://ocharles.o...


My actual résumé is written in BSD mandoc: http://r.dakko.us/


Something like #define DATE(month, day, year) ... would help to read dates without program running.


Yes, but if i is divisible by 3, you need to print... oh, never mind.


I did my entire portfolio as a C++ project at one point. Downside is that recruiters and managers looking at my site thought something was wrong on my server and they were seeing the code for the site.

I still have a copy of it online at: http://old.bertjwregeer.com


For bonus points (or not, depending on the recipient's sense of humour) construct a resume that would be suitable for the underhanded C contest (http://www.underhanded-c.org/).


that would be an interesting way for malware to enter - you submit a seemingly innocent resume in C, ask the reader to compile it to see it in pretty colours/formatting. And subtlety pwning the machine in the process.


Just waiting for the resume in Brainfuck


"They're all while loops because shut up, you're overthinking a joke." Just curious what is wrong with all the while loops ?


Nothing, really.

There's an argument that using a 'for' construct groups together the details of the loop (initialization, the loop condition, iteration update), which can aid readability.

(You don't have to search the loop body to find how things change between iterations)

Of course, nothing forces a for() loop to have iteration behavior entirely dependent on only what's in the 'for'. In my experience this seems to be discouraged, however, preferring to use 'for' only when there is a predictable and simple iteration pattern.

In the end, it's just a matter of preference and style. :)


This is great fun, which comes along at a nice time since I'm working through an intro to C class to revive some very stale "real language" skills I've lost years ago. I've studied through some basic dynamic data structures (linked-lists, binary trees, etc.) and was quite surprised at both how much of this I could follow AND how much I learned.


Would be pretty awesome to make a Swift playground version of a CV that is animated and interactive.


I really liked this project and it inspired me to write something similar in OCaml. If you are interested the link is here: https://news.ycombinator.com/item?id=9226093


That's pretty great. It's too bad most HR tools would completely screw that nice formatting, but I'd definitely bring printed copies of that into a dev role interview! That'd break the ice right away.


Great idea, but oops... the data structure thing_t is hideous. It is a "god object" for anything where differently purposed members are union'd. Also, putting char*'s into a union is a terrible idea.


> It is a "god object" for anything where differently purposed members are union'd. Also, putting char*'s into a union is a terrible idea.

They're not differently purposed, the union is just used to define more contextual labels for equivalent fields, and thus make initialisation more readable within each domain.


There are three data structures unsystematically blended into one (that is, one cannot unambiguously tell which fields go into which "virtual" type, and cannot formally check the correctness). The name thing_t is indicative that you indeed cannot really tell what that entity is. This kind of data structure design begs for errors, while being conceptually wrong.


> There are three data structures unsystematically blended into one

The point is that they're not three completely separate data structures, they're a single data structure with context-dependent field labelling.


I get your point, but how to tell "labelling" from typing? Typing depends on semantics. This particular example only works out because the types are all strings, which is a case of sort of loose typing often used in programming. It could've been like this:

  typedef struct {
	union {
		company_t * company;
		school_t * school;
		project_t * project;
	};
	union {
		address_t * location;
		url_t * url;
	};
etc.


Why is putting char* into a union a bad idea?


char* points to some mutable buffer of char's that is supposed to be allocated somewhere. In this particular case they are assigned string literals, but in general it would be quite easy for someone to break consistency of allocations/deallocations in such code.


Given all unioned members are char*, where would the issue come from?


Say, somebody allocated one of the strings with malloc or strdup and put a free(), but mixed up when to call free() and when not.


OK but that's an issue with using char* where does the union issue come in? I could understand an issue with a union between char* and, say, int or some other pointer, but I don't see what specific issue would arise from a union between the exact same pointer types.


  union {
    char* x;
    char* y;
  };

  ...

  if (/*smth*/) {
    x = "Hello";
  } else {
    y = strdup("Hello");
  }

  ...

  if (/*smth*/) {
    free(y);
  }
It is possible to screw up the condition to calling free(), especially in more involved code.


And how is this different from

  char* x;

  ...

  if (/*smth*/) {
    x = "Hello";
  } else {
    x = strdup("Hello");
  }

  ...

  if (/*smth*/) {
    free(x);
  }
?

Maybe I'm slow, but I don't see how the union makes a difference.


The difference is that the code may try to execute x = "Hello", and then call free(y) (in case the condition is messed up). So the code will try to free() the pointer to a string literal (because they share the same memory location in the union).


Yes, but you can do x = "Hello" and then call free(x) and get the exact same error without the union. The union doesn't make a difference.


The union does make a difference.

The point I was making is that char*'s (and other pointer types as well) in unions make it harder to track ownership and lifetime by introducing implicit dependencies between data and increasing complexity with more code paths.

It's much easier to reason about the correctness of the code, when the fields of the data structures are mutated linearly and independently. This is especially important when the code is maintained by a number of people over long time.

Unfortunately, the C language does not have the capability to automatically check the correctness of memory management and object lifetimes, so the developer has to do their best to ensure the correctness of the code. In doing so, some coding practices can be better than others.

I am talking about generally good and bad coding practices here, not about the formal correctness of that particular piece of code. If some code works, then obviously it is correct, even if the code is obfuscated, non-human-readable or maintainable.


It would help greatly to add the appropriate accents (aigus) to the title of this submission, as I misread this to refer to "resume in C" as in "resuming a suspended continuation in C" :)


It's pretty slick -- although I don't know a lot of recruiters who can convert unix timestamps to actual dates in their heads!


That's why they run the program, it formats them.

Pasting in code from the internet and running it is a little scary though. I wonder of we can spot the backdoor here. :P


It's not even running it only. Recruiters need to compile it first. :)


So it also acts as a good qualified for the company being applied to, then .. pretty wise. Why work at a place that can't compile this?


What if you need a job because your company decided to do a massive layoff?

Usually I would have fun at dumb recruiting processes, but right now I'm in no condition to be picky about getting a new job.


You can use ideone.com

http://ideone.com/Tr7oiN


How about storing the structures in linked lists, and printing them recursively?


Cute, but it probably wouldn't make it past the HR filter.


That in itself is a good selector for companies where HR wields more power than the engineering.


True, but there is a disclaimer:

> No, I don't distribute my résumé like this.


I bet HR people will love this


Cute. Effort I'm willing to spend parsing a resume: zero. Not sure if this is a net win or not.


> thing_t * thing

This makes my head hurt!

I'd prefer thing_t *thing.

But it's a nice concept


Surely it should be `thing_t* thing`!


thing_t* thing_ptr, thing

imo, the star should be with the variable name. because, in this case, a reader could think that both thing_ptr and thing are pointer to a thing_t

"thing_t thing_ptr, thing, other_thing_ptr" is imo more readable


If one uses the entity* notation, they must always declare each variable with a separate declaration, that is:

  thing_t* ptr1;
  thing_t* ptr2;
not

  thing_t* ptr1, ptr2 /* <= oops */;


This could be good idea, if it would lead to omission those retarded screenings.




Registration is open for Startup School 2019. Classes start July 22nd.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: